Building an ADB2C Angular App with @azure/msal-angular

Ash Prosser
5 min readJan 11, 2020

For the past 5 months I’ve been having fun times with Microsoft Azures’ ADB2C. I was tasked with rewriting our companies client-facing platform which had a basic authentication implementation but it wasn’t “quite right”. What followed was 5 months of what felt like endless suffering.

Don’t get me wrong, now that it’s working, I quite like it. I just wish Microsoft would get their libraries in order so that the journey didn't need to be so utterly painful.

There’s no reasons for other devs to suffer. So here I am on a Friday night, blogging about how you can use ADB2C too without losing half your hair like I did.

Disclaimer: This post assumes you have successfully set up a basic ADB2C policy. I will not be covering the Policy side of things just yet… maybe another day.

At time of writing, the @azure/msal-angular version on NPM is currently 0.1.4. I will update this guide in the future if anything drastic changes.

This guide also assumes you know a bit of Angular.

First things first, install the library

npm install @azure/msal-angular — save

Now here’s where we already deviate from the recommended guide..

Create a file to store your MSAL configuration. Like… msal-config.ts. Populate it accordingly with the parameters described in the README.md.

import { environment } from 'src/environments/environment';
import { MsalConfig } from '@azure/angular';
const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.
navigator.userAgent.indexOf("Trident/") > -1;
export const AppMsalConfig: MsalConfig = {
clientID: environment.ADB2C.clientID,
consentScopes: environent.ADB2C.Scopes,
authority: environment.ADB2C.signupAuthority,
validateAuthority: false,
protectedResourceMap: [
[environment.API, environent.ADB2C.Scopes]
],
storeAuthStateInCookie: isIE
redirectUri: 'http://localhost:3000/'
...
}

For an explanation on clientID, consentScopes and authority, read the docs.

I leave validateAuthority as false as I always have hell with it.

protectedResourceMap is required by the interceptor to attach the auth tokens from MSAL for your API. It takes an array where the first value is the API Url, the second argument is the scopes.

storeAuthStateInCookie is enabled if the user is on IE. See here.

redirectUri must be listed on your applications allowed URLs list. If you leave this blank, it will resolve to whatever view you came from. This can pull a prank or two on some apps because if someone has a path bookmarked, that path will be your redirectUri. So make sure you include a wildcard or set all redirectUris’ to the root of your app.

Add the MSAL Module to your app module (usually app.module.ts)

import { NgModule } from ‘@angular/core’;
import { MsalModule } from ‘@azure/msal-angular’;
import { AppMsalConfig } from ‘./msal-config.ts’;
@NgModule({
imports: [
MsalModule.forRoot(AppMsalConfig),

]
})
export class AppModule {}

But.. we’re not done yet! See Q6 here.

Because MSAL uses iFrames to manage tokens, it essentially opens your app again in an iFrame. This can cause absolute carnage so we want a barebones app to load if we’re under an iFrame.

To do this, we make another app module. ie. msal-app.module.ts

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MsalModule } from '@azure/msal-angular';
import { AppMsalConfig } from './msal-config.ts';
import { MsalComponent } from './msal.component.ts';
@NgModule({
imports: [
MsalModule.forRoot(AppMsalConfig)
RouterModule.forRoot([])
],
declarations: [
MsalComponent
],
entryComponents: [
MsalComponent // Not needed in Angular 9+
]
})
export class MsalAppModule {}

What is MsalComponent? It’s a super simple component that takes the place that our usual entry component would be. It looks like this:

import { Component} from '@angular/core';
import { MsalService } from '@azure/msal-angular';

@Component({
selector: 'app-root',
template: '',
})
export class MsalComponent {
constructor(protected msal: MsalService) {
}
}

We inject MsalService because it does lots of behind-the-scenes malarkey to manage these tokens and whatnot.

To load this new barebones module when your app is loaded in an iFrame, change your main.ts as follows

import { AppModule } from './app/app.module';
import { MsalAppModule } from './app/msal-app.module';
...if (environment.production) {
enableProdMode();
}
const isMsalRequest = (window !== window.parent && !window.opener);platformBrowserDynamic().bootstrapModule(isMsalRequest ? MsalAppModule : AppModule);

You’ll note that this is a different approach than what the wiki suggests. This is because our ‘msal’ iframe app must be bare-bones. If your app loads, say, NGRX effects, these can interfere with the auth process.

Thats the hacky stuff done. You can now follow steps 2 and 3 in the @azure/msal-angular readme to secure your app with MsalGuard and MsalInterceptor.

Handling ‘Forgot my Password’

Firstly, we’ll make a fresh MSAL UserAgentApplication just for resetting passwords.

import { InjectionToken } from '@angular/core';
import { UserAgentApplication } from 'msal';
import { environment } from 'src/environments/environments';
import { HubMsalConfig } from '../msal-config';
export const MsalPasswordResetApplicationToken = new InjectionToken<UserAgentApplication>('[MSAL] Password Reset Application', {
providedIn: 'root',
factory: () => new UserAgentApplication(
environment.ADB2C.clientId,
environment.ADB2C.passwordResetAuthority,
() => {},
{
validateAuthority: false,
redirectUri: HubMsalConfig.redirectUri
})
});

I’ve chosen to reuse the redirectUri. You can use whatever you like.. but make sure you add it to the list of allowed Urls for your application.

I’ve also used an InjectionToken, because for your unit tests you will need to be able to mock the UserAgentApplication.

Now, as I said, a password reset is actually an authentication failure. I use NGRX Effects for this, but nothing stops you just putting it in your AppComponent . It just has to be live immediately on app bootstrap.

import { BroadcastService } from '@azure/msal-angular';
...
constructor(
private broadcastService: BroadcastService,
@Inject(MsalPasswordResetApplicationToken) private msalPasswordResetApplication: UserAgentApplication
)
...
this.broadcastService.getMSALItem()
.pipe(
filter(message => message.type === 'msal:loginFailure'),
map(message => message.payload),
subscribe(err => {
if (this.isResetPasswordRequired(err)) {
// Clear all query params else we end up in a redirect loop with ADB2C.
await this.router.navigate(['/'], { queryParams: {}, fragment: undefined});
return this.msalPasswordResetApplication.loginRedirect(ADB2C_SCOPES);
}
return this.msalService.loginRedirect(environment.ADB2C.Scopes);
}
);
private isResetPasswordRequired(error: any) {
return (error && error.errorDesc && error.errorDesc.indexOf('AADB2C90118') !== -1)
}
...

Sorry for Mediums formatting… but basically we’re using the our msalPasswordReset UserAgentApplication when an error is thrown with the password reset code. Other errors will throw out user back to the login screen.

Password changes and other custom authorities

It’s the same drill for any other authority you want to use. Password changes for example.

import { InjectionToken } from '@angular/core';
import { UserAgentApplication } from 'msal';
import { environment } from 'src/environments/environments';
import { HubMsalConfig } from '../msal-config';
export const MsalPasswordChangeApplicationToken = new InjectionToken<UserAgentApplication>('[MSAL] Password Change Application', {
providedIn: 'root',
factory: () => new UserAgentApplication(
environment.ADB2C.clientId,
environment.ADB2C.passwordChangeAuthority,
() => {},
{
validateAuthority: false,
redirectUri: HubMsalConfig.redirectUri
})
});

You can then use it wherever you like as such

...constructor(
@Inject(MsalPasswordChangeApplicationToken) private msalPasswordChangeApplication: UserAgentApplication
)
...
this.msalPasswordChangeApplication.loginRedirect(ADB2C_SCOPES);
...

And that’s it!

End notes

I will update this blog with an example repo when I can. If you have any further issues, feel free to make an issue on the MSAL Github repo and link it here. I will take a look as soon as I can.

--

--

Ash Prosser

Senior Developer for Animal Friends Insurance. Writing about Angular/C# .NET Core/Node.js