Using ADAL with Angular2


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

clip_image008[6]

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.

Comments (34)

  1. Prakash says:

    I’m using systemjs.config.js approach (not webpack), I get 404 error when try to load adal.js

    Failed to load resource: the server responded with a status of 404 (Not Found)
    Error loading http://localhost:25849/expose-loader?AuthenticationContext!http://localhost:25849/node_modules/adal-angular/lib/adal.js as “expose-loader?AuthenticationContext!../../../node_modules/adal-angular/lib/adal.js” from http://localhost:25849/app/services/adal.service.js

    I have also added mappings for expose-loader in systemjs file, am I missing anything else?

    1. Vishal Saroopchand says:

      Prakash,
      Please check your system.js file to ensure it is correctly configured. The below snippet shows a typical system.js configuration for ADAL.
      (function (global) {
      System.config({
      paths: {
      ‘npm:’: ‘node_modules/’
      },
      map: {
      ‘adal’: ‘npm:adal-angular/lib’
      },
      packages: {
      adal: { main: ‘adal.js’, defaultExtension: ‘js’ },
      }
      });
      })(this);

      1. Prakash says:

        Vishal, Thanks for the response.
        Upgraded my project to webpack to get pass through this issue, now stuck with something else.

        JWT token missing some values.
        “appid” – (It should be client id from app registration)
        “aud” – <> (this should be http://someone.onmicrosoft.com/CortanaWebAPI instead of app_id)

        I guess because of these missing values in JWT, WebAPI receiving Identity as “Anonymous”.

        My config is as shown below:

        const endpoints = {
        “https://localhost:44383/”: “http://someone.onmicrosoft.com/CortanaWebAPI”
        };
        public get getAdalConfig(): any {
        return {
        tenant: ‘someone.onmicrosoft.com’,
        clientId: ‘9d9cd8dd-087a-4e87-b3f9-6e7ded2b5e82’,
        redirectUri: window.location.origin + ‘/’,
        postLogoutRedirectUri: window.location.origin + ‘/’,
        endpoints: endpoints
        };
        }

        1. Vishal Saroopchand says:

          Are you using the Microsoft.AspNetCore.Authentication.JwtBearer middleware? Can you paste a snippet of your configuration on the WebAPI?

  2. Prakash says:

    How do we inject http provider to adal service (AuthenticationContext)?

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

    In AngularJs, we can pass it as a second argument to init method.

    adalProvider.init({
    instance: ‘https://login.microsoftonline.com/’,
    tenant: ‘someone.onmicrosoft.com’,
    clientId: ‘9d9cd8dd-087a-4e87-b3f9-6e7ded2b5e82’,
    resourceId: ‘http://ljayaprakashgmail.onmicrosoft.com/CortanaWebAPI’,
    endpoints: endpoints
    }, $httpProvider););

    1. Vishal Saroopchand says:

      The short answer is you cannot supply $httpprovider as you did with adal-angular.js. for http interceptors. There are no angular2/4 interceptor equivalent; however, you can add logic in adal.service.ts as a work around.

  3. praveen says:

    loginResource: ‘https://graph.microsoft.com/’ not working. How can I get access_token for multiple resource

    1. Vishal Saroopchand says:

      Please double check the scopes you have selected on your AAD registered application. Go to the application (Azure Portal > Azure Active Directory > App registration > select your app >Required Permissions (under API Access settings > Add Microsoft Graph >Select your scopes. You can read more about Scopes here: https://msdn.microsoft.com/en-us/library/azure/ad/graph/howto/azure-ad-graph-api-permission-scopes

  4. Marten says:

    I can’t figure out how to get the clientId and Tenant for my app registration in azure, something I am missing here?

    1. Vishal Saroopchand says:

      Marten, the following link has instructions on how to register your application with Azure Active Directory. See https://docs.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-how-to-configure-active-directory-authentication

  5. Brandon Koch says:

    If I wanted to allow the user to input a custom tenant and client id in a form, how would I go about doing that since the adal.service is injected at runtime? Any help would be appreciated. Thanks!

    1. Vishal Saroopchand says:

      Brandon, you can create a service which will be used to store the tenant/client id’s. Use a custom guard to see if these values are set. If not, redirect to your custom page to collect the tenant/client ids. After validating, initiate the OAuth handshake.

  6. Alex says:

    I am using ng2-adal library for some time now. https://github.com/ranveeraggarwal/ng2-adal-QuickStart . However. there questions related to actually user friendly use of Azure Active Directory. Oour clients are finding that really complex to use Azure Portal and add custom domain or any new users. Also, it is not clear how to store company location and name and get it displayed at the screen. They would like to have very simple pages to be able to see list of users, add new user and etc. Any ideas?

    1. Vishal Saroopchand says:

      You can use the Graph API to accomplish this. Here are some links with some additional information.
      https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-graph-api
      https://msdn.microsoft.com/Library/Azure/Ad/Graph/api/api-catalog

      Use the Graph Explorer tool to get acclimated with the REST calls.
      https://graphexplorer.azurewebsites.net/

  7. Michael Conner says:

    Hi

    I’m pulling your example into VS2017 dotnet core project — but namespace adal is not recognized. I’ve installed the adal typescript definition and confirm it’s there in my node modules folder. Any suggestion would be welcome!

    1. Geoff says:

      I was able to solve this problem by placing this line at the top of the typings.d.ts file: ///

        1. Geoff says:

          Sorry this page seems to think this is an HTML tag.

          ///&ltreference path=”./../node_modules/@types/adal/index.d.ts”/&gt

  8. Geoff says:

    I’m having a problem with the second part:

    import ‘expose-loader?AuthenticationContext!../../../node_modules/adal-angular/lib/adal.js’;
    let createAuthContextFn: adal.AuthenticationContextStatic = AuthenticationContext;

    I’m getting the error ERROR in ../authentication/adal.service.ts (5,61): Cannot find name ‘AuthenticationContext’.

    1. Spencer says:

      I just ran into this same problem, had a lot of searches come up empty. Ultimately found some semi-related articles that referenced adding references to the top of your typings.d.ts file. That is what I wound up doing, and it was the following line that did the trick –
      ///

      Hopefully that saves someone else some time!

      1. Spencer says:

        Well, as someone else found, this comment system doesn’t like greater than/less than. Lets try the line again with (gt)/(lt) subbed in –

        /// (gt)reference types=”../node_modules/@types/adal” /(lt)

  9. krzyhook says:

    Practicing Azure AD with Angular, I created repository https://github.com/krzyhook/angular-adal-example where ng2-adal and ADAL.JS are used. Maybe someone will find it helpful. It created with angular-cli.

    1. Vishal Saroopchand says:

      Good stuff! I also updated the original source with a sample service and both ASP.NET5 and ASP.NET Core API layers.

    2. I have tried the example above in an angular-cli project and I get the following error in adal.service.ts:
      ERROR in C:/SourceCode/Gateway/TestAOT/src/app/shared/adal/adal.service.ts (7,26): Cannot find namespace ‘adal’.
      ERROR in C:/SourceCode/Gateway/TestAOT/src/app/shared/adal/adal.service.ts (7,61): Cannot find name ‘AuthenticationContext’.
      ERROR in C:/SourceCode/Gateway/TestAOT/src/app/shared/adal/adal.service.ts (13,22): Cannot find namespace ‘adal’.

      Does anyone have any idea what the issue might be?

  10. Sudhakaran Soundararajan says:

    I have hosted my angular 2 application in azure and provided the authentication. When i click on application url automatically it is going to login page and once logged in it is navigated to our application.
    But i am unable to get the logged in user profile. Please help me using adal services.

  11. LastTribunal says:

    How do handle token expiration?

    1. Vishal Saroopchand says:

      The adal library provides the foundation to renew tokens. You can call AuthenticationContext.acquireToken using a global timer or by hooking into route guards.

      1. Mauri says:

        Can you provide an example? That is not working for me.

  12. Damir Dobric says:

    Getting following error:
    “TypeError: createAuthContextFn is not a constructor”

  13. Hi!
    I was able to replicate this on an angular 4 app, except for one thing: when returning from login on AD, it always goes to the login page.
    I can see in the console that it passes in the home component, but then it goes to login again and I can’t figure out why! What am I missing here ?

    1. geoff says:

      Which browser are you trying this with?

  14. Mauri says:

    Hello, Im started using the ADAL.js, I was able to config this demo fine. And get the data from my azure AD.
    Now the users on my AD has groups. But when I check the userInfo there is no data related to that.
    How do I get the group of the user? I have to query the Azure Graph to get that info?

    Thanks!

    1. Vishal Saroopchand says:

      Correct, you need to query Graph API. Here is a sample project for reference on accessing GraphAPI: https://github.com/Azure-Samples/active-directory-dotnet-graphapi-web
      Also, check the GraphAPI documentation for additional information.

Skip to main content