oidc-client to configure discovery documentation from the local host or other URL

会有一股神秘感。 提交于 2020-01-07 08:24:09

问题


Is their any way to configure discovery document from local host before the login using OIDC-Client in angular 8 application.

I have this manager which is a helper call for the OIDC client

export class AuthenticationService {

  @Output() initialized: boolean = false;

  static USER_LOADED_EVENT = "USER_LOADED";
  static USER_UNLOADED_EVENT = "USER_UNLOADED";
  //static USER_SIGNED_OUT_EVENT = "USER_SIGNED_OUT";
  //static USER_EXPIRED_EVENT = "USER_EXPIRED";
  static USER_RESET_EVENT = "USER_RESET";

  private manager: UserManager;
  private user: User = null;
  private accessToken: Object = null;
  private signingOut: boolean = false;

  private listeners: Object;
  private eventsSubject: Subject<any>;
  private events: Observable<any>;

  public settings: UserManagerSettings;

  constructor(
    private $log: Logger,
    private tokenHelper: TokenHelperService,
    private ngZone: NgZone, private oauthService: OAuthService) {

    //Hook up some event notifications
    this.listeners = {};
    this.eventsSubject = new Subject<any>();

    this.events = from(this.eventsSubject);

    this.events.subscribe(
      ({ name, args }) => {
        if (this.listeners[name]) {
          for (let listener of this.listeners[name]) {
            listener(...args);
          }
        }
      });
  }

  async serviceIsReady(): Promise<void> {

    await new Promise((resolve, reject) => {
      const source = timer(0, 100).subscribe(t => {
        if (this.initialized) {
          source.unsubscribe();
          resolve(true);
        }
        else if (t > 5000) {
          source.unsubscribe();
          reject(false);
        }
      }, error => {
        reject(error);
      });
    });
  }

  /**
   * Initializes the OIDC Client ready for use by the application.
   */
  async initialize(openIdSettings: IOpenIdOptions): Promise<void> {

    if (this.initialized) return;
    this.ngZone.runOutsideAngular(() => {

      this.settings = this.getClientSettings(openIdSettings);
      this.manager = new UserManager(this.settings);
      //Persist settings for easy access by the silent-renew iframe
      window["oidc"] = {
        settings: this.settings
      };
    });

    var self = this;

    this.manager.events.addAccessTokenExpiring(() => {
      this.$log.info("IdSvr token expiring", new Date());
    });


    this.manager.events.addAccessTokenExpired(() => {
      this.$log.info("IdSvr token expired", new Date());
      this.logout(false);
      //this.broadcast(AuthenticationService.USER_EXPIRED_EVENT);
      this.broadcast(AuthenticationService.USER_RESET_EVENT);
    });

    this.manager.events.addSilentRenewError(e => {
      this.$log.warn("IdSvr silent renew error", e.message, new Date());
      this.logout(false);
    });

    this.manager.events.addUserLoaded(user => {
      this.$log.info("IdSvr user session is ready", new Date());
      this.accessToken = self.tokenHelper.getPayloadFromToken(user.access_token, false);
      this.user = user;
      this.broadcast(AuthenticationService.USER_LOADED_EVENT, user);
    });

    this.manager.events.addUserUnloaded(() => {
      this.$log.info("IdSvr user session has ended", new Date());
      this.broadcast(AuthenticationService.USER_UNLOADED_EVENT);

      if (!this.signingOut) {
        this.startAuthentication(window.location.pathname + window.location.search);
      }
    });

    this.manager.events.addUserSignedOut(() => {
      this.$log.info("IdSvr user signed out", new Date());
      this.logout(false);
      //this.broadcast(AuthenticationService.USER_SIGNED_OUT_EVENT);
      this.broadcast(AuthenticationService.USER_RESET_EVENT);
    });

    this.user = await this.manager.getUser();

    this.initialized = true;
  }

  /**
   * Gets the Authorization header, to be added to any outgoing requests, that needs to be authenticated.
   */
  getAuthorizationHeaders(): HttpHeaders {
    return new HttpHeaders({ 'Authorization': this.getAuthorizationHeaderValue() });
  }

  /**
   * Checks to see if a user is currently logged on.
   */
  isLoggedIn(): boolean {
    return this.user != null && !this.user.expired;
  }

  /**
   * Gets all the claims assigned to the current logged on user.
   */
  getProfile(): any {
    return this.user.profile;
  }

  /**
   * Gets all the claims assigned to the current logged on user.
   */
  getAccessToken(): any {
    return this.accessToken || this.tokenHelper.getPayloadFromToken(this.user.access_token, false);;
  }

  /**
   * Checks to see if the current logged on user has the specified claim
   * @param claimType The type of the claim the user must be assigned
   * @param value The value of the claim, uses the wildcard "*", if no value provided.
   */
  hasClaim(claimType: string, value?: string): boolean {

    var upperValue = value === undefined || value === null
      ? "*"
      : value.toUpperCase();

    if (this.isLoggedIn()) {
      const claims = this.getAccessToken()[claimType];
      if (!claims)
        return false;
      if (typeof claims === "string")
        return claims.toUpperCase() === upperValue;
      else if (Object.prototype.toString.call(claims) === "[object Array]")
        if (claims.filter((c) => {
          return c.toUpperCase() === upperValue;
        })
          .length >
          0)
          return true;
    }
    return false;
  }

  /**
   * Checks to see if the current logged on user has any of the specified claims
   * @param claimTypes The type of the claim
   * @param value The value of the claim, uses the wildcard "*", if no value provided.
   */
  hasAnyClaim(claimTypes: string[], value?: string) {
    if (this.isLoggedIn())
      return false;
    for (let i = 0; i < claimTypes.length; i++) {
      if (this.hasClaim(claimTypes[i], value))
        return true;
    }
    return false;
  }

  /**
   * Gets the access token of the current logged on user.
   */
  getAuthorizationHeaderValue(): string {
    return `${this.user.token_type} ${this.user.access_token}`;
  }

  /**
   * Initiates the logon process, to authenticate the user using Identity Server.
   * @param returnUrl The route to load, post authentication.
   */
  async startAuthentication(returnUrl: string): Promise<void> {

    await this.manager.clearStaleState();
    await this.manager.signinRedirect({
      data: {
        returnUrl: returnUrl
      }
    }).catch(err => {
      this.$log.debug("IdSvr sign in failed", err);
      return err;
    });
  }

  /**
   * Processes the callback from Identity Server, post authentication.
   */
  async completeAuthentication(): Promise<Oidc.User> {
    let user = await new Promise<Oidc.User>((resolve, reject) => {
      this.ngZone.runOutsideAngular(() => {
        this.manager.signinRedirectCallback().then(user => {
          resolve(user);
        }).catch(error => {
          reject(error);
        });
      });
    });

    this.$log.debug("IdSvr user signed in");
    this.user = user;
    return user;
  }

  // private delay(ms: number): Promise<void> {
  //   return new Promise<void>(resolve =>
  //     setTimeout(resolve, ms));
  // }

  /**
   * Logs out the current logged in user.
   */
  logout(signoutRedirect?: boolean) {
    if (signoutRedirect === undefined || signoutRedirect !== false) {
      this.signingOut = true;
      signoutRedirect = true;
    }

    this.manager.stopSilentRenew();

    this.manager.removeUser().then(() => {
      this.manager.clearStaleState();
      this.$log.debug("user removed");

      if (signoutRedirect) {
        this.manager.signoutRedirect();
      }
    }).catch(err => {
      this.$log.error(err);
    });
  }

  /**
   * Gets the current logged in user.
   */
  async getUser(): Promise<Oidc.User> {
    return await this.manager.getUser();
  }

  /**
   * Gets the Identity Server settings for this client application.
   */
  getClientSettings(configuration: IOpenIdOptions): UserManagerSettings {
    return {
      authority: configuration.authority + '/',
      client_id: configuration.clientId,
      redirect_uri: configuration.redirectUri,
      post_logout_redirect_uri: configuration.redirectUri,
      response_type: configuration.responseType, // "id_token token",
      scope: "openid profile email " + configuration.apiResourceId,
      filterProtocolClaims: true,
      loadUserInfo: true,
      automaticSilentRenew: true,
      monitorSession: true,
      silent_redirect_uri: configuration.silentRedirectUri,
      accessTokenExpiringNotificationTime: 20, //default 60
      checkSessionInterval: 5000, //default 2000
      silentRequestTimeout: 20000//default: 10000 
    };
  }

  on(name, listener) {
    if (!this.listeners[name]) {
      this.listeners[name] = [];
    }

    this.listeners[name].push(listener);
  }

  broadcast(name, ...args) {
    this.eventsSubject.next({
      name,
      args
    });
  }
}

export function authenticationServiceFactory(authService: AuthenticationService, appSettings: AppSettingsService) {
  return async () => {
    await appSettings.serviceIsReady();
    await authService.initialize(appSettings.getOpenIdOptions());
  }
};

All the configuration settings are inside the getClientSettings method.

Due to some security issue, I am not able to read the discovery document from the okta

Access to XMLHttpRequest at 'https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/.well-known/openid-configuration' from origin 'https://localhost:44307' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Related problem link

Access to XMLHttpRequest at 'xxx/.well-known/openid-configuration' from origin 'xxxx' has been blocked by CORS

I am looking for a way to configure the discovery document from other location. So that CORS issue won't appear. Is there any way to configure the discovery document in the OIDC-Client library

Did some research on https://github.com/IdentityModel/oidc-client-js and haven't found the configure setting

Tried this configuration but seems not be working

getClientSettings(configuration: IOpenIdOptions): UserManagerSettings {
    return {
      authority: configuration.authority + '/',
      client_id: configuration.clientId,
      redirect_uri: configuration.redirectUri,
      post_logout_redirect_uri: configuration.redirectUri,
      response_type: configuration.responseType, // "id_token token",
      scope: "openid profile email " + configuration.apiResourceId,
      filterProtocolClaims: true,
      loadUserInfo: true,
      automaticSilentRenew: true,
      monitorSession: true,
      silent_redirect_uri: configuration.silentRedirectUri,
      accessTokenExpiringNotificationTime: 20, //default 60
      checkSessionInterval: 5000, //default 2000
      silentRequestTimeout: 20000,//default: 10000 
      metadata: {
        issuer: 'https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357',
        jwks_uri: 'https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/v1/keys',
        end_session_endpoint: 'https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/v1/logout',
        authorization_endpoint: 'https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/v1/authorize'
      }, signingKeys: ["HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512"]
    };
  }

Reference

https://github.com/IdentityModel/oidc-client-js/issues/275

https://github.com/OHIF/Viewers/issues/616

Here is the discovery documentation that I get from the issuer

https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/.well-known/openid-configuration

{
    "issuer": "https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357",
    "authorization_endpoint": "https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/v1/authorize",
    "token_endpoint": "https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/v1/token",
    "userinfo_endpoint": "https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/v1/userinfo",
    "registration_endpoint": "https://dev-166545.okta.com/oauth2/v1/clients",
    "jwks_uri": "https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/v1/keys",
    "response_types_supported": ["code", "id_token", "code id_token", "code token", "id_token token", "code id_token token"],
    "response_modes_supported": ["query", "fragment", "form_post", "okta_post_message"],
    "grant_types_supported": ["authorization_code", "implicit", "refresh_token", "password"],
    "subject_types_supported": ["public"],
    "id_token_signing_alg_values_supported": ["RS256"],
    "scopes_supported": ["monash-identity-api", "openid", "profile", "email", "address", "phone", "offline_access"],
    "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt", "none"],
    "claims_supported": ["iss", "ver", "sub", "aud", "iat", "exp", "jti", "auth_time", "amr", "idp", "nonce", "name", "nickname", "preferred_username", "given_name", "middle_name", "family_name", "email", "email_verified", "profile", "zoneinfo", "locale", "address", "phone_number", "picture", "website", "gender", "birthdate", "updated_at", "at_hash", "c_hash"],
    "code_challenge_methods_supported": ["S256"],
    "introspection_endpoint": "https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/v1/introspect",
    "introspection_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt", "none"],
    "revocation_endpoint": "https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/v1/revoke",
    "revocation_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt", "none"],
    "end_session_endpoint": "https://dev-166545.okta.com/oauth2/aus1igd7yewoAs4xa357/v1/logout",
    "request_parameter_supported": true,
    "request_object_signing_alg_values_supported": ["HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512"]
}

回答1:


If CORS is blocked then you'll need to run the following steps.

It is not the correct solution though - you should get your bosses + IT team to instead agree to configure Okta in the standard way for an SPA.

  • Configure OIDC Client with no authority url
  • Configure OIDC Client with an explicit issuer + authorization endpoint
  • Configure OIDC Client to not get user info
  • Give OIDC Client the token signing keys as data
  • Use the implicit flow, with response_type=token id_token

Here is a working configuration that I tested with Azure AD, which has CORS limitations that require this type of hack - whereas Okta should not:

         // OIDC Settings that work when there is no CORS support
         const settings = {

            // OIDC client seems to require at least a dummy value for this
            authority: 'x',

            // Supply these details explicitly
            metadata: {
                issuer: 'https://sts.windows.net/7f071fbc-8bf2-4e61-bb48-dabd8e2f5b5a/',
                authorization_endpoint: 'https://login.microsoftonline.com/7f071fbc-8bf2-4e61-bb48-dabd8e2f5b5a/oauth2/authorize',
            },

            // When CORS is disabled, token signing keys cannot be retrieved
            // The keys must be retrieved first by double hopping from the UI to API to Auth Server
            signingKeys: tokenSigningKeys,

            // Turn off calls to user info since CORS will block it
            loadUserInfo: false,

            // The URL where the Web UI receives the login result
            redirect_uri: 'https://web.mycompany.com/spa/',

            // The no longer recommended implicit flow must be used if CORS is disabled
            response_type: 'token id_token',

            // Other OAuth settings
            client_id: '0ed1c9d0-68e7-4acc-abd1-a0efab2643c8',
            scope: 'openid email profile',

        } as UserManagerSettings;
        this._userManager = new UserManager(settings);

To get the token signing keys the UI will need to double hop via your API to the JWKS endpoint. See the following code of mine for an example of how to do this:

  • UI Code to Call API
  • API code to get JWKS Keys

Note that JWKS keys are public information and getting them does not need securing - this is the JWKS Endpoint for my developer Azure account.



来源:https://stackoverflow.com/questions/59418049/oidc-client-to-configure-discovery-documentation-from-the-local-host-or-other-ur

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!