Azure AD authentication in angular using MSAL angular v2 library

MSAL angular v2 support for RxJS 7 in angular 13

MSAL angular v2 dynamic configurations

const environmentConfig = new EnvironmentConfig(environment.apiPath, environment.VersionNumber);

export function getBaseUrl() {
return document.getElementsByTagName('base')[0].href;
}

export function MSALInterceptorConfigFactory(authConfig: AuthenticationConfiguration): MsalInterceptorConfiguration {
const rootApiUrl = environmentConfig.getRootUrl(getBaseUrl());
const protectedResourceMap = new Map<string, Array<string> | null>();
protectedResourceMap.set(`${rootApiUrl}`, authConfig.scopes);

return {
interactionType: InteractionType.Redirect,
protectedResourceMap,
};
}

export function MSALGuardConfigFactory(authConfig: AuthenticationConfiguration): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
authRequest: {
scopes: authConfig.scopes
},
loginFailedRoute: "./login-failed"
};
}

export function MsalInstanceFactory(authConfig: AuthenticationConfiguration): PublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: authConfig.clientId,
authority: authConfig.authority,
redirectUri: authConfig.redirectUri,
postLogoutRedirectUri: authConfig.postLogoutRedirectUri
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: isIE, // Set to true for Internet Explorer 11
},
system: {
loggerOptions: {
loggerCallback: (logLevel, message) => { console.log(message) },
piiLoggingEnabled: true
}
}
})
}


if (environment.production) {
enableProdMode();
}

fetch(`${environmentConfig.getAuthenticationConfigurationsUrl(getBaseUrl())}`)
.then(response =>
response.json()).then(json => {
let authConfig = new AuthenticationConfiguration(json);
if (!authConfig.redirectUri) {
authConfig.redirectUri = getBaseUrl();
}
if (!authConfig.postLogoutRedirectUri) {
authConfig.postLogoutRedirectUri = getBaseUrl();
}
const providers = [
{ provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] },
{ provide: AuthenticationConfiguration, useValue: authConfig },
{ provide: EnvironmentConfig, useValue: environmentConfig },
{
provide: MSAL_INSTANCE, useFactory: MsalInstanceFactory,
deps: [AuthenticationConfiguration]
},
{
provide: MSAL_GUARD_CONFIG,
useFactory: MSALGuardConfigFactory,
deps: [AuthenticationConfiguration]
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: MSALInterceptorConfigFactory,
deps: [AuthenticationConfiguration]
},

];
platformBrowserDynamic(providers)
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
});
  • environmentConfig.getAuthenticationConfigurationsUrl(getBaseUrl()) returns the API endpoint for retrieving the configurations.
  • I configure MSAL to automatically attach the access token for requests against the API using protectedResourceMap. Note that I pass in the value of the scope. This is necessary to ensure the access token carries the appropriate access for calling the API.
"ClientAuthentication": {
"ClientId": "{client id of my angular app as registered in azure ad}",
"Authority": "https://login.microsoftonline.com/{value of client id above}",
"Scopes": [ "api://{client id of api}/access_as_user" ]
}

Using a dedicated authentication service to abstract away dependency on MSAL angular

import { Inject, Injectable, OnDestroy } from '@angular/core';
import { MsalBroadcastService, MsalGuardConfiguration, MsalService, MSAL_GUARD_CONFIG } from '@azure/msal-angular';
import { InteractionStatus, RedirectRequest } from '@azure/msal-browser';
import { Observable, Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { AuthUser } from '../models/auth-user';

@Injectable({
providedIn: 'root'
})
export class AuthService implements OnDestroy {

private _authenticationContext$: Subject<AuthUser | null>;
private readonly _destroying$ = new Subject<void>();

constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
private broadcastService: MsalBroadcastService, private msalService: MsalService
) {
this._authenticationContext$ = new Subject();
this._destroying$ = new Subject<void>();
this.broadcastService.inProgress$.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None),
takeUntil(this._destroying$)
).subscribe(() => {
this.refreshAuthUser();
})
}

get authenticationContext(): Observable<AuthUser | null> {
return this._authenticationContext$.asObservable();
}

ngOnDestroy(): void {
this._destroying$.next(undefined);
this._destroying$.complete();
}

login() {
if (this.msalGuardConfig.authRequest) {
this.msalService.loginRedirect({ ...this.msalGuardConfig.authRequest } as RedirectRequest).subscribe(() => {

});
} else {
this.msalService.loginRedirect();
}
}

logout() {
return this.msalService.logoutRedirect();
}

private refreshAuthUser() {
/**
* If no active account set but there are accounts signed in, sets first account to active account
* To use active account set here, subscribe to inProgress$ first in your component
* Note: Basic usage demonstrated. Your app may require more complicated account selection logic
*/
let activeAccount = this.msalService.instance.getActiveAccount();

if (!activeAccount && this.msalService.instance.getAllAccounts().length > 0) {
let accounts = this.msalService.instance.getAllAccounts();
activeAccount = accounts[0];
this.msalService.instance.setActiveAccount(activeAccount);
}
if (activeAccount) {
this._authenticationContext$.next({
name: activeAccount.name,
id: activeAccount.homeAccountId,
email: activeAccount.username
})
} else {
this._authenticationContext$.next(null);
}
}
}
export class AuthenticationConfiguration {
clientId!: string;
authority!: string;
redirectUri!: string;
postLogoutRedirectUri!: string;
scopes!: string[];

constructor(init?: Partial<AuthenticationConfiguration>) {
Object.assign(this, init);
}
}

export interface AuthUser {
firstName?: string | undefined;
lastName?: string | undefined;
id: string;
email: string;
}

References

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tai Bo

Tai Bo

Backend developer in .NET core. I enjoy the outdoor, hanging out with good friends, reading and personal development.