Issues with OAuth2 and angular-auth2-oidc Library - PKCE Code Flow

2.6k views Asked by At

I'm new in OAuth2 and the angular-auth2-oidc library so bear with me if i make some newbie mistakes.

WHAT I WANT TO ACHIEVE: A user will click on the login link in the home page, he will be sent to the provider's site to login and then redirect back to the home page with token.

RESULTS SO FAR: A user is sent to the provider's site to login and sent back to my site's home page successfuly but I don't get any token. I output some data in the console in the ngOnit() and they are all null, undefined or false.

Inside the ngOnit() function also i output some events and the events I get in de console is token_refresh_error. I don't do any token refresh in my code explicitely but i still get this error.

Under the Error sub-heading of this post I have added screenshots about the errors.

Here are my config and implementation:

Here is the openid configuration of the my OAuth2 provider:

authorization_endpoint                  "https://DOMAIN_PROVIDER/op/v1/auth"
code_challenge_methods_supported    
    0                                   "plain"
    1                                   "S256"
end_session_endpoint                    "https://DOMAIN_PROVIDER/op/v1/logout"
grant_types_supported   
    0                                    "authorization_code"
id_token_signing_alg_values_supported   
    0                                    "RS256"
issuer                                   "https://DOMAIN_PROVIDER/op"
jwks_uri                                 "https://DOMAIN_PROVIDER/op/v1/keys"
response_modes_supported    
    0                                    "query"
    1                                    "fragment"
    2                                    "form_post"
response_types_supported    
    0                                    "code"
scopes_supported    
    0                                    "openid"
subject_types_supported 
    0                                    "public"
token_endpoint                           "https://DOMAIN_PROVIDER/op/v1/token"
token_endpoint_auth_methods_supported   
    0                                    "client_secret_basic"
    1                                    "client_secret_post"
userinfo_endpoint                        "https://DOMAIN_PROVIDER/op/v1/userinfo"

Here is my implementation:

// Home page component

export class HomeComponent implements OnInit {

  private oAuthConfig: AuthConfig;

  constructor(private oAuthService: OAuthService) { }

   ngOnInit() {

     // IN THIS METHOD I'M OUTPUTTING ALOT OF STUFF TO THE CONSOLE TO SEE IF IT IS WORKING

      if (!this.oAuthService.getAccessToken()) {
         this.initOAuthConfig();
         console.log('__________ACCESS-TOKEN IS NOT SET___________');
      } else {
         console.log('___ACCESS-TOKEN IS SET:', this.oAuthService.getAccessToken());
      }

      console.log('_____GRANTED-SCOPE:', this.oAuthService.getGrantedScopes());
      console.log('______IDENTITY-CLAIMS:', this.oAuthService.getIdentityClaims());
      console.log('______HAS-ACCESS-TOKEN?:', this.oAuthService.hasValidAccessToken());
      console.log('_____HAS-ACCESS-TOKEN?:', this.oAuthService.hasValidIdToken());
      
    // HERE I'M OUTPUTTNG THE EVENTS TO SEE WHAT IS GOING ON
    this.oAuthService.events.subscribe((e: OAuthErrorEvent) => {
      if (e.type === 'token_received') {
        this.logger.log('____________TOKEN RECEIVED');
      }
      this.logger.log('______====================EVENT-TYPE:', e.type);
      this.logger.log('______====================EVENT-REASON:', e.reason);
      this.logger.log('______====================EVENT-PARAMS:', e.params);
    });

  
    if (this.oAuthService.hasValidAccessToken()) {
      this.oAuthService.loadUserProfile().then((t) => {
        console.log('----USER-PROFILE:', t);
      });
    } else {
      this.logger.log('----HAS VALID ACCESS TOKEN');
    }

   }

  
   
   private initOAuthConfig(): void {
    this.oAuthConfig = {
      redirectUri: window.location.origin + '/login/oauth2/myapp',
      postLogoutRedirectUri: window.location.origin + '/logout',
      clientId: 'CLIENT-ID',
      scope: 'openid, rrn, profile',
      oidc: true,
      issuer: 'https://DOMAIN_PROVIDER/op',
      responseType: 'code',
      showDebugInformation: true,
      tokenEndpoint: 'DOMAIN_PROVIDER/op/v1/token',
      jwks: {'keys': ['DOMAIN_PROVIDER/op/v1/keys']}
    };

    this.oAuthService.setStorage(sessionStorage);
    this.oAuthService.configure(this.oAuthConfig);
    this.oAuthService.loadDiscoveryDocumentAndTryLogin();

}

// The class that contains the login function

export class HeaderComponent {

    
   constructor(private oAuthService: OAuthService) { }


    public login() {
     this.oAuthService.initCodeFlow();
    }

    public logout() {
      this.oAuthService.logOut();
    }

}



// The html of the header component which contains the login link

    <a (click)="login()" title="Login">
       Login
    </a>

// Errors / Screenshots of errors

The following are errors in the console when a user is redirected back to the home page

1) In this screenshot the values are null, false or undefined except when discovery document is loaded:

enter image description here

2) In the following screenshot you will see that one of the EVENT-TYPE is token_refresh_error. and the EVENT-REASON is BAD REQUEST. It is trying to make a request to https//DOMAIN_PROVIDER/op/v1/token and is failing though in de initOAuthConfig() method I have added the tokenEndPoint attribute. You can also see that the error is error: "invalid_grant". The second line of the screenshot says Error getting token

I added jwks: {'keys': ['https://DOMAIN_PROVIDER/op/v1/keys']} to initOAuthConfig() thinking that it will solve the problem but it didn't.

enter image description here

// Application tab of the console: Session storage

From the application tab of the console I can see that there are some key-value pairs of data in the session-storage (there is no data in the localstorage) See the next screenshot:

enter image description here

1

There are 1 answers

0
Gary Archer On

I would aim to implement your solution in 2 stages:

  • First understand the OAuth and Open Id Connect design patterns - see my simple TypeScript code sample
  • Think about the best way to translate the sample to Angular, which I'm sure you can do easily enough

CONFIGURATION FILE

I like to express OAuth settings via JSON configuration - see this file. You can update the file to match your configuration, run my sample, then hopefully you will see a token coming back.

API CLIENT RESPONSIBILITY

When the UI calls the API it must first get an access token, and the UI must also handle expired / 401 responses - see this class.

OAUTH CLIENT RESPONSIBILITY

The API client calls this class to get a token, which uses the OIDC Client library to manage redirects, responses and returning tokens.

EXPLANATORY BLOG POST

See also these write ups, which I've aimed to keep visual and easy to read: