Angular How-to: Microsoft ADAL for Angular 6+ with Configurable Settings

Developer Support

Laurie Atkinson, Senior Consultant, Use the microsoft-adal-angular6 wrapper library to authenticate with Azure Active Directory in your Angular 6+ app.

Active Directory Authentication Library (ADAL) for Angular 6+ is a library for integrating Azure AD into your Angular app. However, its provided instructions and example application assume a hardcoded configuration and often your implementation needs to support configurable options. This post provides the modifications necessary to remove this limitation and offer a more realistic scenario.

Get the library

npm install microsoft-adal-angular6

Create a configuration file

Refer to this post for how to set up an editable configuration file that can be customized for multiple environments.

Include a node in your configuration file in the format expected by the microsoft-adal-angular6 library.

The endpoints property will be important for the Angular http interceptor to match which API calls should include the authentication token inserted into the header.

assets\config\config.dev.json

{
    . . .,
    "adalConfig": {
        "clientId": "<client-id-here>",
        "tenant": "<tenant-guid-here>",
        "cacheLocation": "localStorage",
        "endpoints": {
            "api": "<client-id-here>"
        }
    }
}

 

Reference the ADAL module in your app

Do not call forRoot() on the MsAdalAngular6Module as shown in the library’s documentation, because this forces you to provide the adalConfig object too soon in the bootstrapping process. The initialization of the modules listed in the imports section of the module declaration does not wait for the APP_INITIALIZER to complete. Instead declare it this way:

app.module.ts

@NgModule({
   imports: [ MsAdalAngular6Module ],
   declarations: [ . . . ],
   providers: [ . . . ],
   bootstrap: [ AppComponent ]
})
export class AppModule { }

 

Initialize the ADAL configuration

Instead of providing a hardcoded configuration object, retrieve the configuration settings from the JSON file illustrated above using Angular’s APP_INITIALIZER feature. Then specify an alternate provider for the adalConfig parameter to the MsAdalAngular6Service constructor, which returns the retrieved config data instead of a hardcoded parameter.

In addition, add the AuthenticationGuard service which is part of the microsoft-adal-angular6 library.

With these changes, the AppModule should now look as follows:

app.module.ts

import { MsAdalAngular6Module, MsAdalAngular6Service, AuthenticationGuard
  } from 'microsoft-adal-angular6';

let adalConfig: any; // will be initialized by APP_INITIALIZER
export function msAdalAngular6ConfigFactory() {
  return adalConfig; // will be invoked later when creating MsAdalAngular6Service
}

// refer to:
// https://devblogs.microsoft.com/premier-developer/angular-how-to-editable-config-files/
// for a description of the AppConfig service
export function initializeApp(appConfig: AppConfig) {
  const promise = appConfig.load().then(() => {
    adalConfig = {
      tenant: AppConfig.settings.adalConfig.tenant,
      clientId: AppConfig.settings.adalConfig.clientId,
      redirectUri: window.location.origin,
      endpoints: AppConfig.settings.adalConfig.endpoints,
      navigateToLoginRequestUrl: false,
      cacheLocation: AppConfig.settings.adalConfig.cacheLocation
    };
  });
  return () => promise;
}

@NgModule({
   imports: [ MsAdalAngular6Module ],
   declarations: [ . . . ],
   providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: initializeApp,
      deps: [AppConfig],
      multi: true
    },
    MsAdalAngular6Service,
    {
      provide: 'adalConfig',
      useFactory: msAdalAngular6ConfigFactory,
      deps: []
    },
    AuthenticationGuard
   ],
   bootstrap: [ AppComponent ]
})
export class AppModule { }

 

Create an HttpInterceptor to insert the bearer token into API requests

The purpose of the endpoints property on the adalConfig object is to automatically populate the requests matching those endpoints with the token obtained by AAD. My experience is that this insertion does not occur automatically and instead requires a custom interceptor be provided.

insert-auth-token-interceptor.ts

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { mergeMap } from 'rxjs/operators';
import { MsAdalAngular6Service } from 'microsoft-adal-angular6';

@Injectable()
export class InsertAuthTokenInterceptor implements HttpInterceptor {

    constructor(private adal: MsAdalAngular6Service) { }

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        // get api url from adal config
        const resource = this.adal.GetResourceForEndpoint(req.url);
        if (!resource || !this.adal.isAuthenticated) {
            return next.handle(req);
        }

        // merge the bearer token into the existing headers
        return this.adal.acquireToken(resource).pipe(
            mergeMap((token: string) => {
                const authorizedRequest = req.clone({
                    headers: req.headers.set('Authorization', `Bearer ${token}`),
                });
                return next.handle(authorizedRequest);
        }));
    }
}

app.module.ts

@NgModule({
   . . .
   providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: InsertAuthTokenInterceptor
    },
   . . .
})
export class AppModule { }

With these modifications, your Angular app should be ready to start using Azure AD and the ADAL library for authentication. For more details on library usage, refer to the documentation here: https://www.npmjs.com/package/microsoft-adal-angular6.

 

22 comments

Discussion is closed. Login to edit/delete existing comments.

  • Dean Biel 0

    Can `Active Directory Authentication Library (ADAL) for Angular 6+` be used instead of `Microsoft Authentication Library Preview for JavaScript (MSAL.js)`  https://www.npmjs.com/package/msal&nbsp; ?

    • Laurie AtkinsonMicrosoft employee 0

      I assume so, but the other one I have used is “adal-angular’. It is similar, but I needed to create my own Angular AuthService to wrap the methods and I also created my own authentication guard, which comes with the library detailed in this post.

  • Ibtesam Bloch 0

    After going through numerous articles I have finally found yours which does exactly want I want to do. So really appreciate the detailed article.

    However its not working for me. I have applied the same logic as yours, the only difference is where you use the json file for settings from article (https://devblogs.microsoft.com/premier-developer/angular-how-to-editable-config-files/), I have do an web api call to get the settings from appsettings.json from server side. This is so there is consistency and for the ease to setup azure devops.
    The error I get is as below:
    TypeError: Cannot read property ‘displayCall’ of undefinedat new AuthenticationContext (adal.js:140)at push../node_modules/adal-angular/lib/adal.js.module.exports.inject (adal.js:1933)at new MsAdalAngular6Service (microsoft-adal-angular6.js:15)at _createClass (core.js:21253)at _createProviderInstance (core.js:21225)at resolveNgModuleDep (core.js:21189)at _callFactory (core.js:21274)at _createProviderInstance (core.js:21228)at resolveNgModuleDep (core.js:21189)at NgModuleRef_.push../node_modules/@angular/core/fesm5/core.js.NgModuleRef_.get (core.js:21897)

    Any idea what could that be!! I am scratching my head over here so any help will be appreciated. thanks in advance. 

    • Lucas Pein 0

      Hello, I had the same problem. I fixed it by removing any parameter related to adal auth from module constructor. I hope this help you fix it!

  • Ibtesam Bloch 0

    I understand it bit better now. How does this even work for you!!! For me, The provider for MsAdalAngular6Service and the factory gets invoked before the APP_INITIALIZER gets resolved. So the adalconfig is undefined. Surely this cant be because I am trying to get my appsettings through API. I am pulling my hair here (well scarf in my case). Its such a simple and frequently used usecase, I am surprised how Angular hasnt got anything built in to get the settings from server!!!

    • Laurie AtkinsonMicrosoft employee 0

      Is it possible that you are using HttpClient to call your Web API to get the config settings? If so, that call is also invoking the interceptor. One way around that is to use the old Http service or handcraft an AJAX call without Angular and do not insert the auth token for that one API call. That would look something like this:
      export class AppConfig {
        static settings: IAppConfig;  constructor(private http: Http) { }  load() {
          return this.http.get(‘your-api-url’).pipe(tap(resp => {
            AppConfig.settings = resp.json();
            return resp;
          })).toPromise();
      }}

      • Florent Sabbe 0

        As Http is deprecated, you can inject HttpBackend to your AppConfig service and instantiate your own HttpClient.

        import { Injectable } from ‘@angular/core’;
        import { Observable, of, BehaviorSubject } from ‘rxjs’;
        import { tap } from ‘rxjs/operators’;
        import { HttpBackend, HttpClient } from ‘@angular/common/http’;
        const URL = ‘/api/config’;
        @Injectable({ providedIn: ‘root’})export class AppConfigService {
        private appConfigSubject = new BehaviorSubject<any>(null);
        readonly appConfig$ = this.appConfigSubject.asObservable();
        private httpClient: HttpClient;constructor(httpBackend: HttpBackend) {
        // Use local HttpClient to avoid interceptor loop
        this.httpClient = new HttpClient(httpBackend);
        }
        getAppConfig(): Observable<any> {
        return this.httpClient.get(URL).pipe( tap(payload => this.appConfigSubject.next(payload)) );
        }}

      • Sandlin, Shaun 0

        I believe the problem that the original poster was having is related to the resolution of the .then() inside the initializeApp function.

        export function initializeApp(appConfig: AppConfig) {
        const promise = appConfig.load().then(() => {
        adalConfig = {
        tenant: AppConfig.settings.adalConfig.tenant,
        clientId: AppConfig.settings.adalConfig.clientId,
        redirectUri: window.location.origin,
        endpoints: AppConfig.settings.adalConfig.endpoints,
        navigateToLoginRequestUrl: false,
        cacheLocation: AppConfig.settings.adalConfig.cacheLocation
        };
        });
        return () => promise;
        }

        In my attempts, the promise returned from appConfig.load() was resolved (to test, I hardcoded an object to set in order to take http out of the mix) prior to the resolving the msAdalAngular6ConfigFactory function call to initialize the MsAdalAngular6Service.

        export function msAdalAngular6ConfigFactory() {
        return adalConfig; // will be invoked later when creating MsAdalAngular6Service
        }

        I found my order of calls to be this:
        resolve config object in appConfig.load()
        execute msAdalAngular6ConfigFactory, but returns an undefined value as adalConfig has not been initialized yet
        resolve .then(() => {
        adalConfig = {
        tenant: AppConfig.settings.adalConfig.tenant,
        clientId: AppConfig.settings.adalConfig.clientId,
        redirectUri: window.location.origin,
        endpoints: AppConfig.settings.adalConfig.endpoints,
        navigateToLoginRequestUrl: false,
        cacheLocation: AppConfig.settings.adalConfig.cacheLocation
        };
        now adalConfig has a value, but it is too late as the constructor for MsAdalAngular6Service has already run with an undefined config passed in as a parameter.

        I have not used promises much, so maybe I’m missing something here. It looks like the load().then() creates a Promise, which I would think would resolve as a whole. Why is the .then() delayed until after MsAdalAngular6Service initializes?

        Hard coding the object for adalConfig to return worked without a problem, so I’m sure it’s a timing thing with the module creation process.

        Thanks for any help you can provide!

  • Ole Frank Jensen 0

    In Angular 7 this will generate an error:
    ERROR in Error during template compile of ‘AppModule’Only initialized variables and constants can be referenced in decorators because the value of this variable is needed by the template compiler in ‘AppConfig”AppConfig’ is not initialized at app/app.config.ts(7,12).
    Workaround: initialize static field in AppConfig: public static settings: IAppConfig = {} as IAppConfig

  • Prashant Ubale 0

    I have found number of articles for AD integration with Angular and Dot Net Core:
    1. Active Directory Authorization
    2. microsoft-authentication-library-for-js
    3. angular-oauth2-oidc
    4. angular-with-azure-active-directory-authentication-adal-angular4.

    I have implemented first one: 1. Active Directory Authorization
    but it does not work with separate 2 projects as front-end in angular and backend in dot net core. It gives ‘Unauthorized’, so I made it one project in visual studio. but still it is not working.
    After studding all above articles I have one question: In your implementation, Will only this library works for ADAL authentication and it does not require any backend/API chabges?
    If it works from angular, and only windows authentication change is required for backend, then it is great.

    • Shwetha Nayak 0

      Hi Prashant,

      Even i am getting 401 Unauthorised Error.

      I have a query – how do u get correct access token i.e – his.adal.acquireToken(resource) : Here what should be the resource .
      Should it be backend API or https://graph.microsoft.com or what.

  • Liraz Amir 0

    First, Thank you for the awesome guides.

    I have a problem, I have implemented everything exactly the same and it works, but, after a few Http calls it gets automatically logged out and isAuthenticated returns false.

    do you have any idea about what this could be?

    • Liraz Amir 0

      I Got the answer – I used:
      “cacheLocation”: “localStorage”

      I changed it to:
      “cacheLocation”: “sessionStorage”

  • Martin Johansson 0

    Thank you for this post!
    It was exactly what I needed and it works perfectly!

  • Roman Olkhovsky 0

    Why do you need this code ?

    if (!resource || !this.adal.isAuthenticated) {
    return next.handle(req);
    }

    Couldn’t get my application work with this.
    But it works after this part has been removed.

  • Abe IsleemMicrosoft employee 0

    For those of you having trouble getting this to work as a result of the reject(`Could not load file ‘${jsonFile}’: ${JSON.stringify(response)}`) error triggering, I figured it out.

    What was happening is the AppConfig class is making an Http get() request which triggered the HTTP_INTERCEPTORS and therefore calls the InsertAuthTokenInterceptor class. The InsertAuthTokenInterceptor class tries to utilize the MsAdalAngular6Service to inject the bearer token into the request but MsAdalAngular6Service is not set up yet since it’s waiting on the configurations that the AppConfig class is collecting. So, we have a classic circular dependency issue here.

    To reiterate:
    – MsAdalAngular6Service is depending on configs from AppConfig
    – AppConfig utilizes HttpClient as a DI
    – Using HttpClient as a DI will trigger HTTP_INTERCEPTORS
    – HTTP_INTERCEPTORS triggers call InsertAuthTokenInterceptor.Intercept() which depends on MsAdalAngular6Service
    – MsAdalAngular6Service is depending on configs from AppConfig

    To fix this you need to abstain from injecting HttpClient into AppConfig as shown here [https://devblogs.microsoft.com/premier-developer/angular-how-to-editable-config-files/] under “Create a service to read config file”

    Instead, you want to inject HttpBackend and instantiate an HttpClient in the class as follows:

    “`
    private http: HttpClient;

    constructor(private handler: HttpBackend) {
    this.http = new HttpClient(this.handler);
    }
    “`

    Credit goes to deg in this post [https://stackoverflow.com/a/49013534]

    Additionally, this is what your HTTP_INTERCEPTORS should look like:

    “`
    {
    provide: HTTP_INTERCEPTORS,
    useClass: InsertAuthTokenInterceptor,
    multi: true
    }
    “`

Feedback usabilla icon