Switch to loginRedirect — popup blocked by server COOP header

Server-level COOP prevents parent from monitoring popup.location,
so loginPopup never resolves in parent. Redirect flow is the correct
solution: page navigates to Microsoft, returns with #code, and
handleRedirectPromise exchanges the idToken with backend.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-03-20 14:44:19 +00:00
parent 3943be1c51
commit 2c6505f472
3 changed files with 39 additions and 35 deletions

View file

@ -35,6 +35,35 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const navigate = useNavigate();
const { instance, accounts, inProgress } = useMsal();
// Handle Microsoft redirect response on page load (loginRedirect flow)
useEffect(() => {
instance.handleRedirectPromise()
.then(async (response) => {
if (response?.idToken) {
try {
const backendResponse = await authApi.loginWithMicrosoft(response.idToken);
if (backendResponse.data.access_token) {
localStorage.setItem('auth_token', backendResponse.data.access_token);
localStorage.setItem('user', JSON.stringify(backendResponse.data.user));
localStorage.setItem('auth_type', 'microsoft');
setToken(backendResponse.data.access_token);
setUser(backendResponse.data.user);
toast.success('Successfully signed in with Microsoft!');
}
} catch (err: any) {
console.error('Backend Microsoft auth failed:', err);
toast.error('Microsoft sign-in failed', { description: err.message });
}
}
})
.catch((err: any) => {
if (err?.errorCode !== 'no_account_error') {
console.error('MSAL redirect error:', err);
toast.error('Microsoft sign-in failed', { description: err.message });
}
});
}, [instance]);
// Listen for authentication errors and handle navigation
useEffect(() => {
const handleAuthError = (event: Event) => {
@ -211,32 +240,14 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const loginWithMicrosoft = async () => {
setIsMsalLoading(true);
try {
import.meta.env.DEV && console.log('Starting Microsoft authentication...');
const response = await instance.loginPopup(loginRequest);
if (response && response.account && response.idToken) {
const backendResponse = await authApi.loginWithMicrosoft(response.idToken);
if (backendResponse.data.access_token) {
localStorage.setItem('auth_token', backendResponse.data.access_token);
localStorage.setItem('user', JSON.stringify(backendResponse.data.user));
localStorage.setItem('auth_type', 'microsoft');
setToken(backendResponse.data.access_token);
setUser(backendResponse.data.user);
toast.success('Successfully signed in with Microsoft!');
}
}
import.meta.env.DEV && console.log('Starting Microsoft authentication (redirect)...');
await instance.loginRedirect(loginRequest);
// Page navigates away — execution stops here
} catch (error: any) {
console.error('Microsoft login failed:', error);
if (error.errorCode === 'popup_window_error' || error.errorCode === 'user_cancelled') {
toast.error('Sign-in cancelled', { description: 'The sign-in window was closed.' });
} else {
toast.error('Microsoft sign-in failed', {
description: error.message || 'An error occurred during authentication',
});
}
throw error;
} finally {
console.error('Microsoft login redirect failed:', error);
toast.error('Microsoft sign-in failed', {
description: error.message || 'An error occurred during authentication',
});
setIsMsalLoading(false);
}
};
@ -250,7 +261,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// If user was authenticated with Microsoft, also sign out from Microsoft
if (authType === 'microsoft' && accounts.length > 0) {
try {
await instance.logoutPopup({
await instance.logoutRedirect({
account: accounts[0],
postLogoutRedirectUri: window.location.origin + import.meta.env.BASE_URL,
});

View file

@ -2,9 +2,4 @@ import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'
// If loaded inside an MSAL popup, don't render the app.
// The parent window's MSAL instance polls the popup URL for the auth code
// and processes it — no React needed here.
if (window.opener === null || window.opener === window) {
createRoot(document.getElementById("root")!).render(<App />);
}
createRoot(document.getElementById("root")!).render(<App />);

View file

@ -76,10 +76,8 @@ export default function Login() {
async function handleMicrosoftLogin() {
try {
await loginWithMicrosoft();
console.log('Microsoft login successful, navigating to:', from);
navigate(from, { replace: true });
// loginRedirect navigates the page away — no navigate() call needed
} catch (error: unknown) {
// Error handling is done in loginWithMicrosoft function already
console.error('Microsoft login error in form handler:', error);
}
}