2

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 ?

ssougnez
  • 5,315
  • 11
  • 46
  • 79
  • No, you didn't miss that: you have to configure login data flow at any step of app initialization: inside component, service or some factory like APP_INITIALIZER. The doesn't handle it by default: it doesn't read information from url if we didn't ask it. You can also take a look at all examples provided by its author – yurzui Jul 26 '20 at 17:59
  • Most example are about authenticated only app and in that case, it works because the call to the login method is in the service constructor. So I understood you well, my solution is the way to go ? Okay then ^^ – ssougnez Jul 26 '20 at 19:22
  • 1
    Let's take a look at this tutorial https://www.angulararchitects.io/aktuelles/authentication-in-angular-2-with-oauth2-oidc/ Login happens in HomeComponent.login but there is also initialization in AppComponent.constructor. Yes, your solution is the way to go – yurzui Jul 26 '20 at 19:26

1 Answers1

0

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.

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Jeroen
  • 60,696
  • 40
  • 206
  • 339