Using ADAL with Angular2

Developer Support

In this post,  Senior Application Development Manager, Vishal Saroopchand, walks us through an example of ADAL with Angular2.


The primary goal of this post is to give a high level walkthrough on how to use ADAL (Azure AD Authentication Library) with Angular2. ADAL ships with support for Angular1; however, there are no clear guidance for Angular2 at the time of this post. We will focus on the core ADAL.js library without any other dependencies.

Initializing our sample project

We will take a simple bare-bone Angular2 starter as our starting point. To get started clone the angular2-seed starter project from https://github.com/angular/angular2-seed

Next, we want to add ADAL.js, ADAL Type Definitions and expose-loader using NPM

npm install adal-angular --save

npm install expose-loader --save

npm install @types/adal --save-dev

Register your application with your Azure Active Directory

Use the following instructions to create a registration for your sample web app.

https://docs.microsoft.com/en-us/azure/active-directory/active-directory-app-registration

Now that we have all the prerequisites, let’s get started with the Angular2 artifacts.

The ADAL Helper Services

Let’s start by adding a sub folder “services” for all our core services for authentication.

First, we will need to provide configuration information about our AAD application. Add a file config.service.ts with the following

import { Injectable } from '@angular/core';

@Injectable()

export class ConfigService {

constructor() {

    }

public get getAdalConfig(): any {

return {

tenant: 'ENTER YOUR TENANT ID',

clientId: 'ENTER YOUR CLIENT ID',

redirectUri: window.location.origin + '/',

postLogoutRedirectUri: window.location.origin + '/'

        };

    }

}

The important settings are tenant and clientId from your Azure Active Directory application registration.

Next, we need to create a wrapper over ADAL.js and the ApplicationContext object. Add a new file adal.service.ts with the following

import { ConfigService } from './config.service';

import { Injectable } from '@angular/core';

import 'expose-loader?AuthenticationContext!../../../node_modules/adal-angular/lib/adal.js';

let createAuthContextFn: adal.AuthenticationContextStatic = AuthenticationContext;

@Injectable()

export class AdalService {

private context: adal.AuthenticationContext;

constructor(private configService: ConfigService) {

this.context = new createAuthContextFn(configService.getAdalConfig);

    }

login() {

this.context.login();

    }

logout() {

this.context.logOut();

    }

handleCallback() {

this.context.handleWindowCallback();

    }

public get userInfo() {

return this.context.getCachedUser();

    }

public get accessToken() {

return this.context.getCachedToken(this.configService.getAdalConfig.clientId);

    }

public get isAuthenticated() {

return this.userInfo && this.accessToken;

    }

}

Regarding this line of code:

import 'expose-loader?AuthenticationContext!../../../node_modules/adal-angular/lib/adal.js';

Here we use Express Loader to inject Adal.js in the global object. We do this because ADAL does not play nicely with CommonJS pattern. For more information on Express Loader, see https://github.com/webpack-contrib/expose-loader

Next, we will add a CanActivate guard to check if AuthenticationContext.UserInfo is set before loading feature modules. We will use this guard to ensure authenticated users can view our secure assets.

import { Observable } from 'rxjs/Observable';

import { Injectable } from '@angular/core';

import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, NavigationExtras } from '@angular/router';

import { AdalService } from './../services/adal.service';

@Injectable()

export class AuthenticationGuard implements CanActivate {

constructor(private router: Router, private adalService: AdalService) {

    }

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {

let navigationExtras: NavigationExtras = {

queryParams: { 'redirectUrl': route.url }

        };

if (!this.adalService.userInfo) {

this.router.navigate(['login'], navigationExtras);

        }

return true;

    }

}

Handling the OAuth handshake

Our code is partially completed. If we were to call login method on AdalService it will throw an exception as follow:

clip_image002[7]

The callback from the Service Provider is using #/id_token which Angular2 router cannot understand. To address this, we will add a callback route to digest the JWT Token then redirect to our destination page.

Let’s start by adding another folder login-callback with the following components

A Component

import { Component, OnInit } from '@angular/core';

import { Router } from '@angular/router';

import { AdalService } from './../services/adal.service';

@Component({

template: '<div>Please wait...</div>'

})

export class OAuthCallbackComponent implements OnInit {

constructor(private router: Router, private adalService: AdalService) {

    }

ngOnInit() {

if (!this.adalService.userInfo) {

this.router.navigate(['login']);

        } else {

this.router.navigate(['home']);

        }

    }

}

A Guard

import { Injectable } from '@angular/core';

import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

import { AdalService } from './../services/adal.service';

@Injectable()

export class OAuthCallbackHandler implements CanActivate {

constructor(private router: Router, private adalService: AdalService) {

    }

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {

this.adalService.handleCallback();

if (this.adalService.userInfo) {

var returnUrl = route.queryParams['returnUrl'];

if (!returnUrl) {

this.router.navigate(['home']);

            } else {

this.router.navigate([returnUrl], { queryParams: route.queryParams });

            }

        }

else {

this.router.navigate(['login']);

        }

return false;

    }

}

A Module

import { NgModule } from '@angular/core';

import { OAuthCallbackComponent } from './oauth-callback.component';

import { OAuthCallbackHandler } from './oauth-callback.guard';

@NgModule({

imports: [],

declarations: [ OAuthCallbackComponent],

providers: [OAuthCallbackHandler]

})

export class OAuthHandshakeModule { }

Next, we will register the route for “id_token” to be handled by OAuthCallbackComponent and OAuthCallbackGuard. Go to app.routes.ts and add the following Route

{ path: 'id_token', component: OAuthCallbackComponent, canActivate: [OAuthCallbackHandler] },

Also, make login our default route

{ path: '', redirectTo: 'login', pathMatch: 'full' },

We can also guard additional routes by registering the AuthenticationGuard on existing routes

{ path: 'home', component: HomeComponent, canActivate: [AuthenticationGuard] },

Finally, let’s update HomeComponent’s constructor to log the UserProfile and JWT Token as a test

constructor(private adalService: AdalService){

console.log('User info from JWT');

console.log(this.adalService.userInfo);

console.log('JWT Token');

console.log(this.adalService.accessToken);

  }

There are some additional pieces in place but the steps above are the important bits. You can find the complete working example here: https://github.com/vsaroopchand/angular2-seed

The end result

Trigger login in your application by invoking login on AdalService. Here we use a button to trigger the call.

clip_image004[5]

This will redirect to https://login.microsoftonline.com for authentication using OAuth Client Flow

clip_image006[5]

After successfully login, you will redirect back to the application which will handle the JWT token

From here, you can use the JWT Token to query Azure Graph or setting it as Bearer token for delegation to your own backend services.


Premier Support for Developers provides strategic technology guidance, critical support coverage, and a range of essential services to help teams optimize development lifecycles and improve software quality.  Contact your Application Development Manager (ADM) or email us to learn more about what we can do for you.

0 comments

Discussion is closed.

Feedback usabilla icon