When using redirects with MSAL, it is mandatory to handle redirects with either the MsalRedirectComponent
or handleRedirectObservable
. While we recommend MsalRedirectComponent
as the best approach, both approaches are detailed below.
Note that specific guidance has been added for using MSAL Angular v3 with Angular standalone components below.
MsalRedirectComponent
- Subscribing to
handleRedirectObservable
manually - Redirects with standalone components
This is our recommended approach for handling redirects:
@azure/msal-angular
provides a dedicated redirect component that can be imported into your application. We recommend importing theMsalRedirectComponent
and bootstrapping this alongsideAppComponent
in your application on theapp.module.ts
, as this will handle all redirects without your components needing to subscribe tohandleRedirectObservable()
manually.- Pages that wish to perform functions following redirects (e.g. user account functions, UI changes, etc) should subscribe to the
inProgress$
observable, filtering forInteractionStatus.None
. This will ensure that there are no interactions in progress when performing the functions. Note that the last / most recentInteractionStatus
will also be available when subscribing to theinProgress$
observable. Please see our documentation on events for more information on checking for interactions. - If you do not wish to use the
MsalRedirectComponent
, you must handle redirects withhandleRedirectObservable()
yourself, as laid out in the approach below. - See our Angular 15 sample for an example of this approach.
Note that although this is our recommendation for most Angular applications, this approach may not work with Angular standalone components. See the section on redirects with standalone components below for further guidance.
msal.redirect.component.ts
// This component is part of @azure/msal-angular and can be imported and bootstrapped
import { Component, OnInit } from "@angular/core";
import { MsalService } from "./msal.service.ts";
@Component({
selector: 'app-redirect', // Selector to be added to index.html
template: ''
})
export class MsalRedirectComponent implements OnInit {
constructor(private authService: MsalService) { }
ngOnInit(): void {
this.authService.handleRedirectObservable().subscribe();
}
}
index.html
<body>
<app-root></app-root>
<app-redirect></app-redirect> <!-- Selector for additional bootstrapped component -->
</body>
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatListModule } from '@angular/material/list';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';
import { HTTP_INTERCEPTORS, HttpClientModule } 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'; // Redirect component imported from msal-angular
export function loggerCallback(logLevel: LogLevel, message: string) {
console.log(message);
}
export function MSALInstanceFactory(): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: 'b5c2e510-4a17-4feb-b219-e55aa5b74144',
redirectUri: 'http://localhost:4200',
postLogoutRedirectUri: 'http://localhost:4200'
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage
},
system: {
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Info,
piiLoggingEnabled: false
}
}
});
}
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 };
}
@NgModule({
declarations: [
AppComponent,
HomeComponent,
ProfileComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
MatButtonModule,
MatToolbarModule,
MatListModule,
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, MsalRedirectComponent] // Redirect component bootstrapped here
})
export class AppModule { }
app.component.ts
import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
import { MsalBroadcastService, InteractionStatus } from '@azure/msal-angular';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
private readonly _destroying$ = new Subject<void>();
constructor(
private msalBroadcastService: MsalBroadcastService
) {}
ngOnInit(): void {
this.msalBroadcastService.inProgress$
.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None),
takeUntil(this._destroying$)
)
.subscribe(() => {
// Do user account/UI functions here
})
}
This is not our recommended approach, but if you are unable to bootstrap the MsalRedirectComponent
, you must handle redirects using the handleRedirectObservable
as follows:
handleRedirectObservable()
should be subscribed to on every page to which a redirect may occur. Pages protected by the MSAL Guard do not need to subscribe tohandleRedirectObservable()
, as redirects are processed in the Guard.- Accessing or performing any action related to user accounts should not be done until
handleRedirectObservable()
is complete, as it may not be fully populated until then. Additionally, if interactive APIs are called whilehandleRedirectObservables()
is in progress, it will result in aninteraction_in_progress
error. See our document on events for more information on checking for interactions, and our document on errors for details about theinteraction_in_progress
error. - See our older MSAL Angular v2 Angular 9 sample for examples of this approach.
Example of home.component.ts file:
import { Component, OnInit } from '@angular/core';
import { MsalBroadcastService, MsalService } from '@azure/msal-angular';
import { AuthenticationResult } from '@azure/msal-browser';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
constructor(private authService: MsalService) { }
ngOnInit(): void {
this.authService.handleRedirectObservable().subscribe({
next: (result: AuthenticationResult) => {
// Perform actions related to user accounts here
},
error: (error) => console.log(error)
});
}
}
As many Angular applications using standalone components are unable to bootstrap the MsalRedirectComponent
, handleRedirectObservable
must be subscribed to directly. Our recommendation is to subscribe to it in the app.component.ts
file.
- Depending on your application architecture, you may have to subscribe to
handleRedirectObservable()
in other areas as well. - Checking for interactions in progress still applies, please see our document on events for more information on checking for interactions.
- See our Angular standalone sample for examples of this approach.
Example of app.component.ts
file
import { Component, OnInit, Inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule} from '@angular/router';
import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
standalone: true,
imports: [CommonModule, RouterModule]
})
export class AppComponent implements OnInit {
constructor(
@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
private authService: MsalService,
private msalBroadcastService: MsalBroadcastService
) {}
ngOnInit(): void {
this.authService.handleRedirectObservable().subscribe(); // Subscribing to handleRedirectObservable before any other functions both initializes the application and ensures redirects are handled
}
}