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

Premier Developer

Premier

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

 

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

 

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

 

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

app.module.ts

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.

 

Premier Developer
Premier Developer

Premier Support for Developers

Follow Premier   

8 comments

    • Avatar
      Laurie Atkinson

      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.

  • Avatar
    Ibtesam Bloch

    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. 

  • Avatar
    Ibtesam Bloch

    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!!!

    • Avatar
      Laurie Atkinson

      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
        Florent Sabbe

        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)) );
        }}

      • Avatar
        Sandlin, Shaun

        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
    Ole Frank Jensen

    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

Leave a comment