Angular | OAuth2 or Open ID Connect using angular-oauth2-oidc Tutorial with Example Application

In this Angular tutorial, we’ll discuss How to implement the OAuth2 or Open ID Connect Authentication (OIDC) feature using the angular-oauth2-oidc package module in the Angular 11/10/9/8/7/6/5/4 application.

This Angular tutorial is compatible with version 4+ including latest version 12, 11, 10, 9, 8 ,7, 6 & 5.

The angular-oauth2-oidc is a very popular and widely used Angular package to implement the OAuth2 protocol-based authentication. It supports many configurations to easily modify the current flow or use default ones for a quick start.

We’ll also discuss some of the common issues/ challenges faced during the implementation of the OAuth2 / oidc protocol for authentication in Angular application using the angular-oauth2-oidc package module.

Let’s quickly start the implementation…

We have already created a new Angular application named angular-oauth2-demoapp. You can perform the following steps in your current project or quickly create a new Angular project by hitting ng new my-app-name command.

 

Install angular-oauth2-oidc Package

Run the following npm command to install the package module in your Angular project

$ npm i angular-oauth2-oidc-jwks --save

 

Install @auth0/angular-jwt Package

To decode the Access Token, ID Token returned by the IDP to the application, we need to install the @auth0/angular-jwt package module. This will be used as a JWT helper to get these token values.

$ npm i @auth0/angular-jwt

 

Auth Module and Service

By implementing the OAuth2 Authentication inside the application, we want the user to Authenticate by landing on IDP login screen before landing on the actual site.

So, to achieve this we’ll create a separate Auth Module with APP_INITIALIZER provider to call a InitialAuthService method to check if the user is logged in or not otherwise will be redirected for Implicit Flow.

 

Create Initial Auth Service

Run the following ng command to create a new InitialAuthService in the auth folder:

$ ng generate service auth/initial-auth

Update the ~app/auth/initial-auth.service.ts file with the following code

import { Injectable } from "@angular/core";
import { AuthConfig, OAuthService, NullValidationHandler } from "angular-oauth2-oidc";

import { JwtHelperService } from "@auth0/angular-jwt";
import { filter } from "rxjs/operators";
import { Router } from "@angular/router";

@Injectable({
  providedIn: "root",
})
export class InitialAuthService {
  private jwtHelper: JwtHelperService = new JwtHelperService();

  // tslint:disable-next-line:variable-name
  private _decodedAccessToken: any;
  // tslint:disable-next-line:variable-name
  private _decodedIDToken: any;
  get decodedAccessToken() {
    return this._decodedAccessToken;
  }
  get decodedIDToken() {
    return this._decodedIDToken;
  }

  constructor(
    private oauthService: OAuthService,
    private authConfig: AuthConfig,
    public router: Router,
  ) { }

  async initAuth(): Promise<any> {
    return new Promise<void>((resolveFn, rejectFn) => {

      // setup oauthService
      this.oauthService.configure(this.authConfig);
      this.oauthService.setStorage(localStorage);
      this.oauthService.tokenValidationHandler = new NullValidationHandler();

      // subscribe to token events
      this.oauthService.events
        .pipe(filter((e: any) => e.type === "token_received"))
        .subscribe(({ type }) => {
          this.handleNewToken();
        });


      this.oauthService.loadDiscoveryDocumentAndLogin().then(
        (isLoggedIn) => {

          if (isLoggedIn) {
            this.oauthService.setupAutomaticSilentRefresh();
            resolveFn();
          } else {
            this.oauthService.initImplicitFlow();
            rejectFn();
          }
        },
        (error) => {
          console.log({ error });
          if (error.status === 400) {
            location.reload();
          }
        }
      );
    });
  }

  private handleNewToken() {
    this._decodedAccessToken = this.jwtHelper.decodeToken(
      this.oauthService.getAccessToken()
    );

    this._decodedIDToken = this.jwtHelper.decodeToken(
      this.oauthService.getIdToken()
    );
  }

  logoutSession() {
    this.oauthService.logOut();
  }
}

Inside the InitialAuthService, we have jwtHelper service instance to get decoded Access Token and ID Token returned after used authentication after the implicit flow is completed.

The handleNewToken() takes care of assigning these values which in turn is triggered by the token_recieved event returned by OAuthService subscription.

The async initAuth()  method is triggered by AuthModule provider to check if user is logged in or not by triggering the loadDiscoveryDocumentAndLogin() method.

If used is logged in ( checked based on localStorage session ) then we trigger the setupAutomaticSilentRefresh() the resolve the promise to land the user to the application.

Otherwise, the initImplicitFlow() is executed to perform Implicite flow where the user is moved to the IdP login screen to put credentials and get authenticated.

Next, update the AuthModule to call the initAuth() method in this service.

 

Create Auth Module

Create an AuthModule under folder ~app/auth/auth.module.ts by hitting the following ng command

$ ng generate module auth

Then update it with this code

import { APP_INITIALIZER, NgModule } from "@angular/core";
import { AuthConfig, OAuthModule, OAuthStorage } from "angular-oauth2-oidc";
import { InitialAuthService } from "./initial-auth.service";
import { environment } from "../../environments/environment";

const configAuthZero: AuthConfig = environment.idp;

// We need a factory, since localStorage is not available during AOT build time.
export function storageFactory(): OAuthStorage {
  return localStorage
}

@NgModule({
  imports: [OAuthModule.forRoot()],
  providers: [
    InitialAuthService,
    { provide: AuthConfig, useValue: configAuthZero },
    { provide: OAuthStorage, useFactory: storageFactory },
    {
      provide: APP_INITIALIZER,
      useFactory: (initialAuthService: InitialAuthService) => () =>
        initialAuthService.initAuth(),
      deps: [InitialAuthService],
      multi: true,
    },
  ],
})
export class AuthModule { }

In this AuthModule we’re triggering InitialAuthService method initAuth() to check user logged-in status. Here we’re also consuming the angular-oauth2-oidc configuration properties, which we need to define inside the environment.ts file as shown below:

let clientid = "my_app_id";
let secret = "your_app_secret";
let issuer = "https://myidpportal.com/idp/myapp/";
let logoutUrl = "https://myidpportal.com/idp/myapp/logout.html?ClientID=";
 
export const environment = {​​​​
  production: false,
  idp: {​​​​
    issuer: issuer,
    redirectUri: "https://mysite.com",
    clientId: clientid,
    scope: "openid profile email",
    responseType: "code",
    showDebugInformation: true,
    dummyClientSecret: secret,
    logoutUrl: logoutUrl+clientid,
    skipIssuerCheck:true
  }​​​​,
}​​​​;

Issue Resolved

Sometimes you may face an issue throwing this kind of error in the console and authentication does not happen:

oauthservice.ts:613 invalid issuer in discovery document expected: https://myidpportal.com/idp/myapp/ current: myapp

This issue occurs when the issuer URL in your Service Provider is set to ‘myapp‘ but you are sending the full URL in the issuer property i.e https://myidpportal.com/idp/myapp/

 

To resolve this mismatch in the issuer property you can add the skipIssuerCheck to true

 

Update App Module

Now, we have created our own AuthModule, but this needs to be imported inside the AppModule as shown below:

// app.module.ts
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AuthModule } from './auth/auth.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    AuthModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Also, we have added HttpClientModule to make HTTP calls from Angular application to issuer URL.

 

Source Code

Find source code at GitHub repo here.

 

Conclusion

We discussed how to quickly add OAuth2 authentication in Angular application with industrial standards for which we used the most common and widely used angular-oauth2-oidc plugin.

Also, we discussed a common issue faced when the issuer URL doesn’t match with the configuration inside the Service Provider. In the tutorial, we’ll learn how to get claims like EmailAddress username passed by IdP to the application inside the JWT token. if you are getting only an ID token then make sure to enable send ID token as JWT which will have all the claims like user information to be passed by IdP to the application side.

We’ll also learn how to pass the JWT token with HTTP Rest API calls to authenticate the genuine origin source.

Leave a Reply

Your email address will not be published. Required fields are marked *