diff --git a/src/Utils/AuthorizationUtils.ts b/src/Utils/AuthorizationUtils.ts index 3ec288b11..1b556a26f 100644 --- a/src/Utils/AuthorizationUtils.ts +++ b/src/Utils/AuthorizationUtils.ts @@ -147,18 +147,20 @@ export async function acquireMsalTokenForAccount( }); } } - // For Mooncake, popups to login.partner.microsoftonline.cn are blocked by COOP headers - // (ERR_BLOCKED_BY_RESPONSE). Use loginRedirect instead for interactive sign-in. - // Silent attempts (prompt: "none") are skipped on Mooncake for the same reason — - // ssoSilent already failed above, so there is nothing more to try silently. + // For Mooncake, popups to login.partner.microsoftonline.cn are blocked by COOP headers. + // Use loginRedirect when running standalone (not in an iframe) so the top-level page can + // navigate away and return. Inside a portal iframe, redirect is forbidden by MSAL + // (redirect_in_iframe), so we fall back to loginPopup — MSAL v5's redirect bridge + // handles COOP via BroadcastChannel rather than window.opener, so the popup still works. const isMooncake = configContext.AAD_ENDPOINT === Constants.AadEndpoints.Mooncake; - if (isMooncake) { + const isInIframe = window !== window.parent; + if (isMooncake && !isInIframe) { if (silent) { - // ssoSilent already failed; a prompt:none popup would also be blocked on Mooncake. + // ssoSilent already failed; a redirect cannot be used silently. // Re-throw so the caller knows silent acquisition was not possible. throw new Error("Silent login not possible on Mooncake; interactive sign-in required."); } - // Interactive: navigate the browser to the Mooncake AAD login page. + // Standalone (hosted explorer): navigate the page to the Mooncake AAD login URL. // On return, handleRedirectPromise() in getMsalInstance() will cache the token. await msalInstance.loginRedirect({ prompt: "login", ...loginRequest }); // Browser has navigated away; this line is never reached. @@ -211,12 +213,16 @@ export async function acquireTokenWithMsal( } catch (silentError) { if (silentError instanceof msal.InteractionRequiredAuthError && silent === false) { // Sovereign cloud environments (e.g., Azure China / Mooncake) block popups in some - // browser configurations. Use redirect-based flow instead to ensure compatibility. + // browser configurations. Use redirect-based flow instead when running standalone. // Detection is based on configContext.AAD_ENDPOINT (set from the portal's serverId message // via updateAADEndpoints), NOT window.location.host — the latter is unreliable when running // the explorer locally (localhost) embedded inside portal.azure.cn. + // Inside a portal iframe, redirect is forbidden by MSAL (redirect_in_iframe error), so + // we fall back to acquireTokenPopup — MSAL v5's redirect bridge handles COOP via + // BroadcastChannel (not window.opener) so the popup works even from within an iframe. const isMooncake = configContext.AAD_ENDPOINT === Constants.AadEndpoints.Mooncake; - if (isMooncake) { + const isInIframe = window !== window.parent; + if (isMooncake && !isInIframe) { try { // acquireTokenRedirect navigates the browser away; execution does not continue // past this await. On return, getMsalInstance()'s handleRedirectPromise() will diff --git a/web.config b/web.config index e987fa939..e63cb1469 100644 --- a/web.config +++ b/web.config @@ -65,6 +65,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +