Safari Browser which throws this error BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. #5668
I am seeing this error right after login in Safari browser. I have disabled cookie blocks and disabled prevent cross site tracking in my safari browser but I am still not able to login using safari. It is working fine on all other browsers. Any help with this is greatly appreciated.
MSAL Configuration
**
* This file contains authentication parameters. Contents of this file
* is roughly the same across other MSAL.js libraries. These parameters
* are used to initialize Angular and MSAL Angular configurations in
* in app.module.ts file.
*/
import { LogLevel, Configuration, BrowserCacheLocation } from '@azure/msal-browser';
import { applicationConfig } from './app.config';
import { ClientConfig } from './client-config';
declare const window: Window;
const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1;
const isSafari = ((navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1));
let currentConfig: ClientConfig;
currentConfig = getClientConfig()
function getClientConfig() : ClientConfig {
switch (window.location.hostname) {
case getHostName(applicationConfig.production.clientUrl):
currentConfig = applicationConfig.production;
break;
case getHostName(applicationConfig.uat.clientUrl):
currentConfig = applicationConfig.uat;
break;
case getHostName(applicationConfig.qa.clientUrl):
currentConfig = applicationConfig.qa;
break;
case getHostName(applicationConfig.staging.clientUrl):
currentConfig = applicationConfig.staging;
break;
default:
currentConfig = applicationConfig.local;
}
return currentConfig;
}
function getHostName(url: string): string {
return url.replace('https://', '').replace('/', '');
}
/**
* Configuration object to be passed to MSAL instance on creation.
* For a full list of MSAL.js configuration parameters, visit:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md
*/
export const msalConfig: Configuration = {
auth: {
clientId: currentConfig.clientId, // This is the ONLY mandatory field that you need to supply.
authority: 'https://login.microsoftonline.com/mytenantid', // Defaults to "https://login.microsoftonline.com/common"
redirectUri: currentConfig.redirectUri, //'https://localhost:4200', // Points to window.location.origin by default. You must register this URI on Azure portal/App Registration.
postLogoutRedirectUri: currentConfig.postLogoutRedirectUri,//'https://localhost:4200', // Points to window.location.origin by default.
clientCapabilities: ['CP1'],// This lets the resource server know that this client can handle claim challenges.
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage, // Configures cache location. "sessionStorage" is more secure, but "localStorage" gives you SSO between tabs.
storeAuthStateInCookie: isIE || isSafari, // Set this to "true" if you are having issues on IE11 or Edge. Remove this line to use Angular Universal
},
system: {
/**
* Below you can configure MSAL.js logs. For more information, visit:
* https://docs.microsoft.com/azure/active-directory/develop/msal-logging-js
*/
loggerOptions: {
loggerCallback(logLevel: LogLevel, message: string) {
console.log(message);
},
logLevel: LogLevel.Trace,
piiLoggingEnabled: true
}
}
}
/**
* Add here the endpoints and scopes when obtaining an access token for protected web APIs. For more information, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md
*/
export const protectedResources = {
backendAPI: {
endpoint: currentConfig.apiUri,
scopes: {
role:[currentConfig.roleScope]]
}
},
msgraphApi: {
endpoint: "https://graph.microsoft.com/v1.0",
scopes: {
UserRead:["User.Read"]
}
}
}
/**
* Scopes you add here will be prompted for user consent during sign-in.
* By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
* For more information about OIDC scopes, visit:
* https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
*/
export const loginRequest = {
scopes: ['openid', 'profile', 'email', 'User.Read' ]
};
Relevant Code Snippets
import { BreakpointObserver } from '@angular/cdk/layout';
import { Component, OnInit, Inject, OnDestroy, ViewChild, AfterViewInit, OnChanges, ElementRef, HostBinding } from '@angular/core';
import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
import { AuthenticationResult, EventMessage, EventType, InteractionStatus, InteractionType, PopupRequest, RedirectRequest } from '@azure/msal-browser';
import { UserLoginStatusService } from './services/user-login-status.service';
import { Subject, Subscription } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { APIService } from './services/api.service';
import { Profile } from './models/profile';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { MatSidenav } from '@angular/material/sidenav';
import { FormControl } from '@angular/forms';
import { OverlayContainer } from '@angular/cdk/overlay';
import { version } from './utils/version';
import { PreventBackService } from './services/prevent-back.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
title = 'Faculty Management System';
isIframe = false;
loginDisplay = false;
sub$: Subscription[] = [];
private readonly _destroying$ = new Subject<void>();
profile?: Profile;
profilePicture?: SafeUrl;
year: number = new Date().getFullYear();
version = version.buildNumber;
//Used by dark mode toggle
@HostBinding('class') className = '';
toggleControl = new FormControl(false);
@ViewChild(MatSidenav)
sideNav!: MatSidenav;
constructor(private observer: BreakpointObserver,
@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
private authService: MsalService,
private msalBroadcastService: MsalBroadcastService,
private userLoginStatusService: UserLoginStatusService,
private apiService: APIService,
private domSanitizer: DomSanitizer,
private overlay: OverlayContainer,
private preventBackService: PreventBackService
) {
}
ngOnInit(): void {
this.preventBackService.preventBackButton();
//First check on initail load if dark mode is enabled or when page is refreshed
this.toggleControl.setValue(localStorage.getItem('darkMode') === 'true' ? true : false);
this.className = localStorage.getItem('darkMode') === 'true' ? 'darkMode' : '';
if (this.className === 'darkMode') {
this.overlay.getContainerElement().classList.add('darkMode');
}
else {
this.overlay.getContainerElement().classList.remove('darkMode');
}
//Check if dark mode is enabled or disabled based on toggle
this.toggleControl.valueChanges.subscribe((darkMode) => {
const darkClassName = 'darkMode';
this.className = darkMode ? darkClassName : '';
localStorage.setItem('darkMode', darkMode ? 'true' : 'false');
if (darkMode) {
this.overlay.getContainerElement().classList.add(darkClassName);
} else {
this.overlay.getContainerElement().classList.remove(darkClassName);
}
});
this.isIframe = window !== window.parent && !window.opener;
this.setLoginDisplay();
this.authService.instance.enableAccountStorageEvents(); // Optional - This will enable ACCOUNT_ADDED and ACCOUNT_REMOVED events emitted when a user logs in or out of another tab or window
/**
* You can subscribe to MSAL events as shown below. For more info,
* visit: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-angular/docs/v2-docs/events.md
*/
this.msalBroadcastService.msalSubject$.pipe(
filter((msg: EventMessage) => msg.eventType === EventType.ACCOUNT_ADDED || msg.eventType === EventType.ACCOUNT_REMOVED),
takeUntil(this._destroying$),
).subscribe((result: EventMessage) => {
if (this.authService.instance.getAllAccounts().length === 0) {
window.location.pathname = "/";
} else {
this.setLoginDisplay();
}
});
this.msalBroadcastService.inProgress$
.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None),
takeUntil(this._destroying$)
)
.subscribe(() => {
this.setLoginDisplay();
this.checkAndSetActiveAccount();
//this.callbackEnd();
});
}
// callbackEnd() {
// this.authService.handleRedirectObservable().subscribe({
// next: (result: AuthenticationResult) => {
// if (result?.accessToken != null) {
// //only call this endpoint if the user is logged in for the first time.
// this.sub$.push(this.userLoginStatusService.getUserInfo().subscribe(() => { }));
// }
// },
// error: (error) => console.log(error)
// });
// }
setLoginDisplay() {
this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
this.userLoginStatusService.isUserLoggedIn.next(this.loginDisplay);
if (this.authService.instance.getAllAccounts().length > 0) {
this.getProfile();
this.getProfilePicture();
}
}
checkAndSetActiveAccount() {
/**
* If no active account set but there are accounts signed in, sets first account to active account
* To use active account set here, subscribe to inProgress$ first in your component
* Note: Basic usage demonstrated. Your app may require more complicated account selection logic
*/
let activeAccount = this.authService.instance.getActiveAccount();
if (!activeAccount && this.authService.instance.getAllAccounts().length > 0) {
let accounts = this.authService.instance.getAllAccounts();
// add your code for handling multiple accounts here
this.authService.instance.setActiveAccount(accounts[0]);
this.getProfile();
this.getProfilePicture();
}
}
login() {
if (this.msalGuardConfig.interactionType === InteractionType.Popup) {
if (this.msalGuardConfig.authRequest) {
this.authService.loginPopup({ ...this.msalGuardConfig.authRequest } as PopupRequest)
.subscribe((response: AuthenticationResult) => {
this.authService.instance.setActiveAccount(response.account);
});
} else {
this.authService.loginPopup()
.subscribe((response: AuthenticationResult) => {
this.authService.instance.setActiveAccount(response.account);
});
}
} else {
this.msalBroadcastService.inProgress$
.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None),
)
.subscribe(() => {
if (this.msalGuardConfig.authRequest) {
this.authService.loginRedirect({ ...this.msalGuardConfig.authRequest } as RedirectRequest);
} else {
this.authService.loginRedirect();
}
});
}
}
logout() {
const activeAccount = this.authService.instance.getActiveAccount() || this.authService.instance.getAllAccounts()[0];
if (this.msalGuardConfig.interactionType === InteractionType.Popup) {
this.authService.logoutPopup({
account: activeAccount,
});
} else {
this.authService.logoutRedirect({
account: activeAccount,
});
}
sessionStorage.clear();
localStorage.clear();
}
// unsubscribe to events when component is destroyed
ngOnDestroy(): void {
this._destroying$.next(undefined);
this._destroying$.complete();
this.sub$.map(x => x != null ? x.unsubscribe() : null);
}
//Role related methods
hasRole(role: string): boolean {
return this.apiService.hasRole(role);
}
ngAfterViewInit(): void {
//Called after ngAfterContentInit when the component's view has been initialized. Applies to components only.
setTimeout(() => {
this.observer.observe(['(max-width: 800px)']).subscribe((result: any) => {
if (result.matches) {
this.sideNav.mode = 'over';
this.sideNav.close();
}
else {
this.sideNav.mode = 'side';
this.sideNav.open();
}
})
}, 0);
}
getProfile() {
let profile = localStorage.getItem('profile');
if (profile != null || profile != undefined) {
this.profile = JSON.parse(profile);
}
else {
this.sub$.push(
this.userLoginStatusService.getUserProfile().subscribe(profile => {
this.profile = profile;
localStorage.setItem('profile', JSON.stringify(profile));
})
);
}
}
getProfilePicture() {
let profilePicture = localStorage.getItem('profilePicture');
if (profilePicture != null || profilePicture != undefined) {
this.profilePicture = this.domSanitizer.bypassSecurityTrustResourceUrl('data:image/jpeg;base64,' + profilePicture);
}
else {
this.sub$.push(
this.userLoginStatusService.getUserPhoto().subscribe({
next: (photo) => {
let reader = new FileReader();
reader.readAsDataURL(photo);
reader.onload = () => {
this.profilePicture = this.domSanitizer.bypassSecurityTrustResourceUrl(reader.result as string);
if (this.profilePicture == null) {
this.profilePicture = this.domSanitizer.bypassSecurityTrustResourceUrl('assets/default-user.jpg');
}
if (reader.result != null) {
localStorage.setItem('profilePicture', reader.result.toString().split(',')[1]);
}
};
},
error: (error) => {
this.profilePicture = this.domSanitizer.bypassSecurityTrustResourceUrl('assets/default-user.jpg');
}
})
);
}
}
}
Core Library
MSAL.js v2 (@azure/msal-browser)
Core Library Version
2.32.2
Wrapper Library
MSAL Angular (@azure/msal-angular)
Wrapper Library Version
2.5.2
Public or Confidential Client?
Public
Description
I am seeing this error right after login in Safari browser. I have disabled cookie blocks and disabled prevent cross site tracking in my safari browser but I am still not able to login using safari. It is working fine on all other browsers. Any help with this is greatly appreciated.
MSAL Configuration
Relevant Code Snippets
Identity Provider
None
Source
External (Customer)