microsoft-authentication-library-for-js
microsoft-authentication-library-for-js copied to clipboard
Logout does not work with Application Single sign-on configuration
Core Library
MSAL.js (@azure/msal-browser)
Core Library Version
2.26.0
Wrapper Library
MSAL Angular (@azure/msal-angular)
Wrapper Library Version
2.3.2
Public or Confidential Client?
Public
Description
I have Application SingleSignOn session behavior in my custom Azure AD B2C policy. I also have Angular SPA, it uses msal library. I can not logout. When I press logout, user is redirected to main page, after that it is redirected to login page and automatic sign in is performed. When I change scope to Tenant or Suppressed , it works okay.
Error Message
No response
MSAL Logs
No response
Network Trace (Preferrably Fiddler)
- [ ] Sent
- [ ] Pending
MSAL Configuration
return new PublicClientApplication({
auth: {
authority: b2cPolicies.authorities.signUpSignIn.authority,
clientId: environment.msalClientId,
knownAuthorities: [environment.authorityName],
postLogoutRedirectUri: window.location.origin,
redirectUri: `/`,
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE, // set to true for IE 11
},
});
Relevant Code Snippets
Custom Azure AD B2C policy Signup_Signin
<UserJourneyBehaviors>
<SingleSignOn Scope="Application" KeepAliveInDays="30"/>
<SessionExpiryType>Absolute</SessionExpiryType>
<SessionExpiryInSeconds>1200</SessionExpiryInSeconds>
<JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="046c8d7d-66fe-493e-9493-cc4f46cf065b" DeveloperMode="true" ClientEnabled="false" ServerEnabled="true" TelemetryVersion="1.0.0" />
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
Typescript code:
import { HttpErrorResponse } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { MSAL_GUARD_CONFIG, MsalBroadcastService, MsalGuardConfiguration, MsalService } from '@azure/msal-angular';
import {
AccountInfo,
AuthenticationResult,
InteractionRequiredAuthError,
InteractionStatus,
RedirectRequest,
SilentRequest,
} from '@azure/msal-browser';
import { Observable, of, Subject } from 'rxjs';
import {
catchError, delay, filter, map, switchMap, take, tap,
} from 'rxjs/operators';
import { environment } from '../../../../environments/environment';
import { AzureErrorCodes } from '../../enum';
import { apiConfig } from '../apiconfig';
import { TokenService } from './token.service';
@Injectable()
export class AuthService {
private _authenticationTokenSubject$: Subject<void> = new Subject<void>();
constructor(
@Inject(MSAL_GUARD_CONFIG) private _msalGuardConfig: MsalGuardConfiguration,
private _tokenService: TokenService,
private _msalService: MsalService,
private _msalBroadcastService: MsalBroadcastService,
) {
this._msalBroadcastService.inProgress$.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None),
switchMap(() => this.getAuthenticationToken()),
).subscribe();
}
public isAuthenticated(): boolean {
return Boolean(this._msalService.instance.getActiveAccount());
}
public loginRedirect(): Observable<void> {
if (this._msalGuardConfig.authRequest) {
return this._msalService.loginRedirect({ ...this._msalGuardConfig.authRequest } as RedirectRequest);
}
return this._msalService.loginRedirect();
}
public logout(): void {
this._logout().subscribe();
}
public unauthorize(): void {
this._msalService.logout();
this._tokenService.clearToken();
}
public getAuthenticationToken(): Observable<string> {
this.checkAndSetActiveAccount();
const params: SilentRequest = {
account: this._msalService.instance.getActiveAccount(),
scopes: apiConfig.b2cScopes,
};
return this._msalService.acquireTokenSilent(params).pipe(
switchMap((response: AuthenticationResult) => {
if (new Date(response.expiresOn) > new Date()) {
this._tokenService.token = response.accessToken;
this._authenticationTokenSubject$.next();
return response.accessToken;
}
throw new Error('Token is expired');
}),
catchError((error: HttpErrorResponse) => {
if (error instanceof InteractionRequiredAuthError) {
if (error.errorMessage.includes(AzureErrorCodes.SessionNotExist)) {
return this._logout().pipe(map(() => ''));
}
if (error.errorMessage.includes('interaction_required')) {
return this._msalService.acquireTokenRedirect(params).pipe(map(() => ''));
}
if (error.errorCode !== 'block_token_requests' &&
error.errorCode !== 'user_cancelled' &&
error.errorCode !== 'login_progress_error') {
throw error;
}
} else if (error.message === 'Token is expired') {
return this.loginRedirect().pipe(map(() => ''));
} else {
return this._msalService.acquireTokenRedirect(params).pipe(map(() => ''));
}
return of('');
}),
);
}
public checkAndSetActiveAccount(): void {
const account = this._msalService.instance.getAllAccounts().find(
(item: AccountInfo) => item.environment === environment.authorityName,
);
if (account) {
this._msalService.instance.setActiveAccount(account);
}
}
public waitForAuthenticationToken(): Observable<void> {
return this._authenticationTokenSubject$.asObservable().pipe(take(1));
}
private _logout(): Observable<void> {
const delayTime = 2000;
return this._msalService.logout().pipe(
tap(() => this._tokenService.clearToken()),
delay(delayTime),
switchMap(() => this.loginRedirect()),
);
}
}
Reproduction Steps
- Set SingleSignOn Scope="Application" in Azure AD B2C Signup_Signin custom policy;
- Implement authorization in Angular App using msal.js with code provided;
- Log In;
- Log Out;
Expected Behavior
User is logged out, session is ended. Log In page is displayed.
Identity Provider
Azure B2C Custom Policy
Browsers Affected (Select all that apply)
Chrome, Firefox, Edge
Regression
No response
Source
External (Customer)