Fix auth flow: switch from popup to redirect-based MSAL login
The popup login flow was broken because the Flask 302 redirect from / to /reporting/index caused MSAL in the popup to consume the auth code hash before the parent window could detect it, leaving the parent stuck on "Authenticating..." while the popup rendered the full app. - Switch signIn() from loginPopup() to loginRedirect() - Add handleRedirectPromise() at start of initAuth() to process the auth code on page load after returning from Microsoft - Change root route from 302 redirect to direct template render so the #code=... hash fragment is preserved for MSAL - Switch signOut() from logoutPopup() to clearCache() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
677736943a
commit
1dff8fece5
2 changed files with 56 additions and 43 deletions
9
app.py
9
app.py
|
|
@ -109,12 +109,13 @@ def create_app(config_class=app_config.Config):
|
|||
app.register_blueprint(video_master_bp)
|
||||
logger.info("Video Master blueprint (BETA) registered at /video-master")
|
||||
|
||||
# Register root route (redirect to reporting)
|
||||
# Register root route - render reporting page directly (no 302 redirect).
|
||||
# A redirect would strip the URL hash fragment (#code=...) that MSAL
|
||||
# needs to process after returning from Microsoft login.
|
||||
@app.route('/')
|
||||
def root():
|
||||
"""Redirect root to reporting module."""
|
||||
from flask import redirect, url_for
|
||||
return redirect(url_for('reporting.index'))
|
||||
"""Render reporting index directly at root."""
|
||||
return render_template('reporting/index.html', active_tab='reporting')
|
||||
|
||||
# Register error handlers
|
||||
register_error_handlers(app)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* Authentication Module for HM QC Report Dashboard
|
||||
* Uses Microsoft MSAL Browser library for Azure AD authentication
|
||||
* Uses redirect flow (not popup) to avoid cross-window timing issues.
|
||||
*/
|
||||
|
||||
// Azure AD Configuration
|
||||
|
|
@ -101,7 +102,9 @@ async function checkAuthStatus() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sign in with Microsoft
|
||||
* Sign in with Microsoft using redirect flow.
|
||||
* The page navigates to Microsoft login, then redirects back.
|
||||
* On return, handleRedirectPromise() in initAuth() processes the response.
|
||||
*/
|
||||
async function signIn() {
|
||||
if (!msalInstance) {
|
||||
|
|
@ -111,35 +114,13 @@ async function signIn() {
|
|||
|
||||
try {
|
||||
showLoading();
|
||||
|
||||
// Perform popup login
|
||||
const loginResponse = await msalInstance.loginPopup(loginRequest);
|
||||
|
||||
if (loginResponse && loginResponse.idToken) {
|
||||
// Send token to backend for validation and cookie storage
|
||||
const success = await submitTokenToBackend(loginResponse.idToken);
|
||||
|
||||
if (success) {
|
||||
currentUser = loginResponse.account;
|
||||
isAuthenticated = true;
|
||||
showAuthenticatedState();
|
||||
updateUserInfo();
|
||||
} else {
|
||||
throw new Error('Failed to validate token with backend');
|
||||
}
|
||||
}
|
||||
// Redirect the whole page to Microsoft login.
|
||||
// After auth, Microsoft redirects back to redirectUri.
|
||||
// initAuth() -> handleRedirectPromise() will pick up the response.
|
||||
await msalInstance.loginRedirect(loginRequest);
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error);
|
||||
|
||||
if (error.errorCode === 'popup_window_error') {
|
||||
showError('Popup was blocked. Please allow popups for this site.');
|
||||
} else if (error.errorCode === 'user_cancelled') {
|
||||
console.log('User cancelled login');
|
||||
showUnauthenticatedState();
|
||||
} else {
|
||||
showError('Login failed: ' + (error.errorMessage || error.message || 'Unknown error'));
|
||||
}
|
||||
|
||||
console.error('Login redirect failed:', error);
|
||||
showError('Login failed: ' + (error.errorMessage || error.message || 'Unknown error'));
|
||||
showUnauthenticatedState();
|
||||
}
|
||||
}
|
||||
|
|
@ -181,7 +162,7 @@ async function signOut() {
|
|||
try {
|
||||
showLoading();
|
||||
|
||||
// Clear backend cookie
|
||||
// Clear backend session
|
||||
await fetch('/auth/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
|
|
@ -189,12 +170,10 @@ async function signOut() {
|
|||
|
||||
// Clear MSAL cache
|
||||
if (msalInstance) {
|
||||
const currentAccount = msalInstance.getActiveAccount();
|
||||
if (currentAccount) {
|
||||
await msalInstance.logoutPopup({
|
||||
account: currentAccount,
|
||||
postLogoutRedirectUri: window.location.origin
|
||||
});
|
||||
const accounts = msalInstance.getAllAccounts();
|
||||
if (accounts.length > 0) {
|
||||
// Clear local MSAL state without redirecting to Microsoft logout
|
||||
msalInstance.clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +252,10 @@ function showError(message) {
|
|||
if (!errorDiv) {
|
||||
errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'alert alert-danger mt-3';
|
||||
authRequired.querySelector('.text-center').appendChild(errorDiv);
|
||||
const textCenter = authRequired.querySelector('.text-center');
|
||||
if (textCenter) {
|
||||
textCenter.appendChild(errorDiv);
|
||||
}
|
||||
}
|
||||
errorDiv.textContent = message;
|
||||
} else {
|
||||
|
|
@ -329,14 +311,44 @@ async function initAuth() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle redirect response from Microsoft login.
|
||||
// After loginRedirect(), the page reloads with auth code in the URL hash.
|
||||
// handleRedirectPromise() extracts the code, exchanges it for tokens,
|
||||
// and cleans up the URL hash.
|
||||
try {
|
||||
const redirectResponse = await msalInstance.handleRedirectPromise();
|
||||
|
||||
if (redirectResponse && redirectResponse.idToken) {
|
||||
console.log('Redirect login successful, sending token to backend...');
|
||||
|
||||
// Send token to backend for session creation
|
||||
const success = await submitTokenToBackend(redirectResponse.idToken);
|
||||
|
||||
if (success) {
|
||||
currentUser = redirectResponse.account;
|
||||
isAuthenticated = true;
|
||||
|
||||
// Setup event listeners and show authenticated state
|
||||
setupEventListeners();
|
||||
showAuthenticatedState();
|
||||
updateUserInfo();
|
||||
return;
|
||||
} else {
|
||||
console.error('Backend token validation failed after redirect');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling redirect response:', error);
|
||||
}
|
||||
|
||||
// Setup event listeners
|
||||
setupEventListeners();
|
||||
|
||||
// Check authentication status
|
||||
// No redirect response - check if we have an existing backend session
|
||||
const authenticated = await checkAuthStatus();
|
||||
|
||||
if (authenticated) {
|
||||
console.log('User is authenticated');
|
||||
console.log('User is authenticated (existing session)');
|
||||
} else {
|
||||
console.log('User is not authenticated');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue