microsoft-authentication-library-for-js
microsoft-authentication-library-for-js copied to clipboard
'Remember Me' does not work with Suppressed 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 implemented Keep Me Signed In according to Microsoft doc: https://learn.microsoft.com/en-us/azure/active-directory-b2c/session-behavior?pivots=b2c-custom-policy#enable-keep-me-signed-in-kmsi. I have Suppressed SingleSignOn in my custom Azure AD B2C policy. I also have Angular SPA,it uses msal library. But it logs me out after 24 hours. When I change scope to Tenant <SingleSignOn Scope="Tenant" />, it works okay.
Also I noticed that sso cookie does not exist for Suppressed scope.
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="Suppressed" 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="Suppressed" in Azure AD B2C Signup_Signin custom policy;
- Enable 'Remember Me' in Azure AD B2C custom policy;
- Implement authorization in Angular App using msal.js with code provieded;
- Select 'Remember Me' during Sign In.
Expected Behavior
User is not prompted to login for 30 days.
Identity Provider
Azure B2C Custom Policy
Browsers Affected (Select all that apply)
Chrome, Firefox, Edge
Regression
No response
Source
External (Customer)