ssougnez
ssougnez

Reputation: 5886

Implement OAuth2 code flow with angular-oauth2-oidc

I'm using angular-oauth2-oidc to implement authorization code flow in an angular 10 application. The main idea is pretty easy, I just have an app component with a button. When the user clicks on it, he must be redirected to the authentication provider login page and back on the application when successfully logged in.

The authentication is handled by the following service:

export class AuthenticationService {
  private authCodeFlowConfig: AuthConfig = {
    issuer: ...
  };

  constructor(public oauthService: OAuthService) {
    this.oauthService.configure(this.authCodeFlowConfig);

    if (location.href.indexOf('?code') > 0) {
      this.oauthService.loadDiscoveryDocumentAndLogin();
    }
  }

  async login(): Promise<void> {
    await this.oauthService.loadDiscoveryDocumentAndLogin();
  }
}

This works but I'm a bit bothered by the URL check in the constructor. However, if I don't do that, the user is correctly redirected to the login page and back on the application once succesfully logged in, but he gets stuck in the middle of the code flow. Indeed, the authorization code is present in the URL, however, angular-oauth2-oidc is not processing it, which is why I have to call again the login method in the constructor if the "code" query string is present.

I suspect that I'm doing something wrong as I was expecting that angular-oauth2-oidc would process the code automatically.

Am I missing something here ?

Upvotes: 5

Views: 6305

Answers (1)

Jeroen
Jeroen

Reputation: 63820

You can check out how my sample would do it with the gist of the login logic like this:

this.oauthService.loadDiscoveryDocument()

  // 1. HASH LOGIN: Try to log in via hash fragment after redirect back from IdServer:
  .then(() => this.oauthService.tryLogin())
  .then(() => {
    if (this.oauthService.hasValidAccessToken()) {
      return Promise.resolve();
    }

    // 2. SILENT LOGIN: Try to log in via a refresh because then we can prevent needing to redirect the user:
    return this.oauthService.silentRefresh()
      .then(() => Promise.resolve())
      .catch(result => {
        // Subset of situations from https://openid.net/specs/openid-connect-core-1_0.html#AuthError
        const errorResponsesRequiringUserInteraction = ['interaction_required', 'login_required', 'account_selection_required', 'consent_required'];

        if (result && result.reason && errorResponsesRequiringUserInteraction.indexOf(result.reason.error) >= 0) {
          console.warn('User interaction is needed to log in, we will wait for the user to manually log in. Could\'ve also forced user to log in here.');
          return Promise.resolve();
        }

        return Promise.reject(result);
      });
  })

  .then(() => {
    this.isDoneLoadingSubject$.next(true);

    if (this.oauthService.state && this.oauthService.state !== 'undefined' && this.oauthService.state !== 'null') {
      let stateUrl = this.oauthService.state;
      if (stateUrl.startsWith('/') === false) {
        stateUrl = decodeURIComponent(stateUrl);
      }
      console.log(`There was state of ${this.oauthService.state}, so we are sending you to: ${stateUrl}`);
      this.router.navigateByUrl(stateUrl);
    }
  })
  .catch(() => this.isDoneLoadingSubject$.next(true));

There should be no need to sniff out a ?code yourself, the library (when instructed as above) will handle that for you.

Upvotes: 1

Related Questions