msal-angular Authenticating Angular app with Azure Active Directory
We can achieve Angular app to authenticate with Azure Active Directory in 2 parts.
Complete App registration with Active Directory.
Utilize the above app registration to implement authentication of the user using the Microsoft Authentication Library (MSAL).
App Registration:
Here are the steps to create SPA app registration got PKCE(Proof Key for Code Exchange) flow:
- Sign in to the Azure portal.
- Open Azure Active Directory.
Under Manage, select App registrations > New registration.
Enter a Name for your application. Users of your app might see this name, and you can change it later. Select Register to create the app registration.
Once Registered, you can get the Authority, ClientId and TenantID for your application. These are important to be included in your SPA for the authentication process.
Let’s move on to the Angular app, If you already have an app great. Or we can quickly build one using angular cli
ng new angular-msal-authentication
npm install @azure/msal-browser @azure/msal-angular@latest
This is just a plain angular app, default with no other html or custom code in it. The above 2 packages provide the code necessary to complete the authentication process.
Add the following code to app.module.ts
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { IPublicClientApplication, PublicClientApplication, InteractionType, BrowserCacheLocation, LogLevel } from '@azure/msal-browser';
import { MsalGuard, MsalInterceptor, MsalBroadcastService, MsalInterceptorConfiguration, MsalModule, MsalService, MSAL_GUARD_CONFIG, MSAL_INSTANCE, MSAL_INTERCEPTOR_CONFIG, MsalGuardConfiguration, MsalRedirectComponent } from '@azure/msal-angular';export function loggerCallback(logLevel: LogLevel, message: string) {
console.log(message);
}const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1;// Creates custom client application to implement login
// functionality
export function MSALInstanceFactory(): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: '<client-id>',
authority: 'https://login.microsoftonline.com/<tenant-id',
redirectUri: 'https://localhost:4200'
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE, // set to true for IE 11
},
system: {
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Info,
piiLoggingEnabled: false
}
}
});
}// provides authRequest configuration
export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
const protectedResourceMap = new Map<string, Array<string>>();
protectedResourceMap.set('https://graph.microsoft.com/v1.0/me', ['user.read']);return {
interactionType: InteractionType.Redirect,
protectedResourceMap
};
}export function MSALGuardConfigFactory(): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
authRequest: {
scopes: ['user.read']
}
};
}@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
MsalModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
{
provide: MSAL_INSTANCE,
useFactory: MSALInstanceFactory
},
{
provide: MSAL_GUARD_CONFIG,
useFactory: MSALGuardConfigFactory
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: MSALInterceptorConfigFactory
},
MsalService,
MsalGuard,
MsalBroadcastService
],
bootstrap: [AppComponent]
})
export class AppModule { }
Add the login and Accesss token request code in app.component.ts file
import { Component, Inject, OnInit } from '@angular/core';import { NavigationEnd, Router } from '@angular/router';
import { MSAL_GUARD_CONFIG, MsalGuardConfiguration, MsalService, MsalBroadcastService } from '@azure/msal-angular';
import { AuthenticationResult, InteractionStatus, InteractionType, PopupRequest, RedirectRequest, SilentRequest } from '@azure/msal-browser';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
var ga: any;
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'Portfolio';
isIframe = false;
loginDisplay = false;
private readonly _destroying$ = new Subject<void>();constructor(public router: Router,
@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
private authService: MsalService,
private msalBroadcastService: MsalBroadcastService
) {}ngOnInit(){
this.isIframe = window !== window.parent && !window.opener;this.msalBroadcastService.inProgress$
.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None),
takeUntil(this._destroying$)
)
.subscribe(() => {
this.setLoginDisplay();
this.login();
});
}setLoginDisplay() {
this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;console.log(this.loginDisplay);
}login() {
if (this.msalGuardConfig.authRequest && this.loginDisplay){
this.msalGuardConfig.authRequest.account = this.authService.instance.getAllAccounts()[0];
this.authService.acquireTokenSilent({...this.msalGuardConfig.authRequest} as SilentRequest)
.subscribe((response: AuthenticationResult) => {
this.authService.instance.setActiveAccount(response.account);
console.log(response.accessToken);
console.log(this.authService.instance.getAllAccounts());
});
} else {
this.authService.loginRedirect();
}
}logout() {
this.authService.logout();
}ngOnDestroy(): void {
this._destroying$.next(undefined);
this._destroying$.complete();
}
}
modify app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { MsalGuard } from '@azure/msal-angular';const routes: Routes = [
{
path: '**',
component: HomeComponent,
canActivate: [
MsalGuard
]
}
];const isIframe = window !== window.parent && !window.opener;
@NgModule({
imports: [RouterModule.forRoot(routes, {
initialNavigation: !isIframe ? 'enabled' : 'disabled'
})],
exports: [RouterModule]
})
export class AppRoutingModule { }
That’s it. Now when we run the code. This will be workflow: