Core Library
MSAL.js (@azure/msal-browser)
Core Library Version
4.16.0
Wrapper Library
MSAL React (@azure/msal-react)
Wrapper Library Version
3.0.16
Public or Confidential Client?
Public
Description
I'm trying to implement sign-up and login with Outlook (Microsoft account) using MSAL.js (@azure/msal-browser@4.16.0) in a Single Page Application running at http://localhost:4200. After the user is redirected back with an authorization code, the token exchange request to https://login.microsoftonline.com/common/oauth2/v2.0/token fails with a CORS error and a 400 Bad Request. The MSAL error reads: post_request_failed: Network request failed: If the browser threw a CORS error, check that the redirectUri is registered in the Azure App Portal as type 'SPA'. I’ve already added http://localhost:4200 as a redirect URI under the SPA platform in the Azure App Portal, and I’m using the Authorization Code Flow with PKCE. Still, I’m seeing this issue during token exchange. I'd appreciate guidance on how to resolve this and ensure a successful login flow with MSAL.
Error Message
MsalAuthProvider.tsx:79 Login failed: BrowserAuthError: post_request_failed: Network request failed: If the browser threw a CORS error, check that the redirectUri is registered in the Azure App Portal as type 'SPA'
https://login.microsoftonline.com/common/oauth2/v2.0/token?client-request-id=019860a7-0b2f-7a9f-bba6-88199b22d062' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
MSAL Logs
No response
Network Trace (Preferrably Fiddler)
MSAL Configuration
{
auth: {
clientId: process.env.NX_MICROSOFT_TEAMS_CLIENT_ID as string,
authority: 'https://login.microsoftonline.com/common',
redirectUri: window.location.origin, // Use dynamic origin
postLogoutRedirectUri: window.location.origin,
navigateToLoginRequestUrl: false,
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false,
},
system: {
allowPlatformBroker: false, // Disables WAM Broker
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
default:
return;
}
},
},
},
}
Relevant Code Snippets
// msalConfig.ts
export const msalInstance = new PublicClientApplication(msalConfig);
export const loginRequest = {
scopes: ['User.Read']
};
// Initialize MSAL instance
let isInitialized = false;
let initPromise: Promise<void> | null = null;
export const initializeMsal = async (): Promise<void> => {
if (isInitialized) return;
if (initPromise) return initPromise;
initPromise = msalInstance.initialize().then(() => {
isInitialized = true;
console.log('MSAL initialized successfully');
}).catch((error) => {
console.error('MSAL initialization failed:', error);
throw error;
});
return initPromise;
};
export const isMsalInitialized = (): boolean => isInitialized;
// MsalAuthProvider.tsx
import React, { createContext, useContext, useEffect, useState } from 'react';
import {
AuthenticationResult,
AccountInfo,
InteractionStatus,
SilentRequest,
EndSessionRequest
} from '@azure/msal-browser';
import { msalInstance, loginRequest } from './msalconfig';
interface MsalContextType {
instance: typeof msalInstance;
accounts: AccountInfo[];
inProgress: InteractionStatus;
isAuthenticated: boolean;
login: () => Promise<AuthenticationResult | null>;
loginSilent: (account: AccountInfo) => Promise<AuthenticationResult | null>;
logout: (account?: AccountInfo) => Promise<void>;
acquireTokenSilent: (request: SilentRequest) => Promise<AuthenticationResult | null>;
}
// Export the context so it can be used elsewhere if needed
export const MsalContext = createContext<MsalContextType | undefined>(undefined);
interface MsalAuthProviderProps {
children: React.ReactNode;
}
export const MsalAuthProvider: React.FC<MsalAuthProviderProps> = ({ children }) => {
const [accounts, setAccounts] = useState<AccountInfo[]>([]);
const [inProgress, setInProgress] = useState<InteractionStatus>(InteractionStatus.Startup);
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
useEffect(() => {
const updateAccountState = () => {
const currentAccounts = msalInstance.getAllAccounts();
setAccounts(currentAccounts);
setIsAuthenticated(currentAccounts.length > 0);
};
// Initial account state
updateAccountState();
// Listen for account changes
const callbackId = msalInstance.addEventCallback((event) => {
if (event.eventType === 'msal:loginSuccess' ||
event.eventType === 'msal:logoutSuccess' ||
event.eventType === 'msal:accountAdded' ||
event.eventType === 'msal:accountRemoved') {
updateAccountState();
}
if (event.eventType === 'msal:loginStart' ||
event.eventType === 'msal:logoutStart') {
setInProgress(InteractionStatus.Login);
} else if (event.eventType === 'msal:loginSuccess' ||
event.eventType === 'msal:loginFailure' ||
event.eventType === 'msal:logoutSuccess') {
setInProgress(InteractionStatus.None);
}
});
setInProgress(InteractionStatus.None);
return () => {
if (callbackId) {
msalInstance.removeEventCallback(callbackId);
}
};
}, []);
const login = async (): Promise<AuthenticationResult | null> => {
try {
setInProgress(InteractionStatus.Login);
const response = await msalInstance.loginPopup(loginRequest);
return response;
} catch (error) {
console.error('Login failed:', error);
throw error;
} finally {
setInProgress(InteractionStatus.None);
}
};
const loginSilent = async (account: AccountInfo): Promise<AuthenticationResult | null> => {
try {
const silentRequest = {
...loginRequest,
account: account,
};
const response = await msalInstance.acquireTokenSilent(silentRequest);
return response;
} catch (error) {
console.error('Silent login failed:', error);
throw error;
}
};
const logout = async (account?: AccountInfo): Promise<void> => {
try {
setInProgress(InteractionStatus.Logout);
const logoutRequest: EndSessionRequest = {
account: account || accounts[0],
postLogoutRedirectUri: window.location.origin,
};
await msalInstance.logoutPopup(logoutRequest);
} catch (error) {
console.error('Logout failed:', error);
throw error;
} finally {
setInProgress(InteractionStatus.None);
}
};
const acquireTokenSilent = async (request: SilentRequest): Promise<AuthenticationResult | null> => {
try {
const response = await msalInstance.acquireTokenSilent(request);
return response;
} catch (error) {
console.error('Token acquisition failed:', error);
throw error;
}
};
const contextValue: MsalContextType = {
instance: msalInstance,
accounts,
inProgress,
isAuthenticated,
login,
loginSilent,
logout,
acquireTokenSilent,
};
return (
<MsalContext.Provider value={contextValue}>
{children}
</MsalContext.Provider>
);
};
export const useMsal = (): MsalContextType => {
const context = useContext(MsalContext);
if (!context) {
throw new Error('useMsal must be used within a MsalAuthProvider');
}
return context;
};
// Export the context type as well for external use
export type { MsalContextType };
Reproduction Steps
//Login.tsx
import { useMsal } from './MsalAuthProvider';
const { login: msalLogin, inProgress, accounts } = useMsal();
// Microsoft/Outlook signup function
const signUpWithOutlook = async () => {
try {
setLoading(true);
const loginResponse = await msalLogin();
if (!loginResponse || !loginResponse.account) {
throw new Error('Login response is null or account is missing');
}
const account = loginResponse.account;
// Extract user data
const email = account.username;
const fullName = account.name || '';
const firstName = fullName.split(' ')[0] || '';
const lastName = fullName.split(' ').slice(1).join(' ') || '';
const microsoftId = account.localAccountId;
const data = {
email: email,
firstName: firstName,
microsoftId: microsoftId,
lastName: lastName,
orgName: email.split('@').pop()?.split('.')[0],
password: '',
microsoftToken: loginResponse.accessToken,
};
........
} catch (error: any) {
setLoading(false);
console.error('Microsoft signup error:', error);
if (error instanceof BrowserAuthError) {
if (error.errorCode === 'user_cancelled') {
setSignUpError('Signup was cancelled by user');
} else if (error.errorCode === 'interaction_in_progress') {
setSignUpError('Another login is in progress. Please wait.');
} else {
setSignUpError('Microsoft signup failed. Please try again.');
}
} else {
setSignUpError('Microsoft signup failed. Please try again.');
}
}
};
Expected Behavior
User should be redirected to Microsoft login, then redirected back with a code, which is exchanged for tokens without error.
Identity Provider
Entra ID (formerly Azure AD) / MSA
Browsers Affected (Select all that apply)
Chrome
Regression
No response
Core Library
MSAL.js (@azure/msal-browser)
Core Library Version
4.16.0
Wrapper Library
MSAL React (@azure/msal-react)
Wrapper Library Version
3.0.16
Public or Confidential Client?
Public
Description
I'm trying to implement sign-up and login with Outlook (Microsoft account) using MSAL.js (@azure/msal-browser@4.16.0) in a Single Page Application running at http://localhost:4200. After the user is redirected back with an authorization code, the token exchange request to https://login.microsoftonline.com/common/oauth2/v2.0/token fails with a CORS error and a 400 Bad Request. The MSAL error reads: post_request_failed: Network request failed: If the browser threw a CORS error, check that the redirectUri is registered in the Azure App Portal as type 'SPA'. I’ve already added http://localhost:4200 as a redirect URI under the SPA platform in the Azure App Portal, and I’m using the Authorization Code Flow with PKCE. Still, I’m seeing this issue during token exchange. I'd appreciate guidance on how to resolve this and ensure a successful login flow with MSAL.
Error Message
MsalAuthProvider.tsx:79 Login failed: BrowserAuthError: post_request_failed: Network request failed: If the browser threw a CORS error, check that the redirectUri is registered in the Azure App Portal as type 'SPA'
https://login.microsoftonline.com/common/oauth2/v2.0/token?client-request-id=019860a7-0b2f-7a9f-bba6-88199b22d062' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
MSAL Logs
No response
Network Trace (Preferrably Fiddler)
MSAL Configuration
Relevant Code Snippets
Reproduction Steps
//Login.tsx
import { useMsal } from './MsalAuthProvider';
const { login: msalLogin, inProgress, accounts } = useMsal();
// Microsoft/Outlook signup function
const signUpWithOutlook = async () => {
try {
setLoading(true);
};
Expected Behavior
User should be redirected to Microsoft login, then redirected back with a code, which is exchanged for tokens without error.
Identity Provider
Entra ID (formerly Azure AD) / MSA
Browsers Affected (Select all that apply)
Chrome
Regression
No response