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 application.
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.
[lwptoc]
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:
oauth–service.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