Describe the bug
When password protection is enabled on an Azure Static Web App, it is impossible to use any OAuth/OpenID Connect
redirect flow (e.g., Azure AD B2C, MSAL). The password protection layer intercepts the OAuth callback redirect and
strips the URL fragment (#code=...&state=...), making it impossible for the client-side library to complete the
token exchange.
Additionally, the StaticWebAppsBasicAuthCookie is not sent on cross-origin navigations back from the identity
provider (likely due to SameSite cookie policy), so even if the user has already authenticated with the password,
they are prompted again on the OAuth callback redirect — creating an infinite loop where the auth code is always
lost.
Route rules with "allowedRoles": ["anonymous"] do not bypass password protection, as the password layer runs before
route rules are evaluated.
To Reproduce
Steps to reproduce the behavior:
- Enable password protection on a Static Web App environment
- Configure an app that uses MSAL.js (or any OAuth library) with a redirect URI pointing to a route on the SWA
(e.g., /auth/callback)
- Add a route rule to allow anonymous access to the callback path:
{
"routes": [
{
"route": "/auth/callback*",
"allowedRoles": ["anonymous"]
}
]
}
- Visit the app → enter the SWA password → land on the login page
- Click "Sign in" → get redirected to the identity provider (e.g., Azure AD B2C)
- Authenticate successfully → identity provider redirects back to
/auth/callback#code=...&state=...
- SWA intercepts the redirect → redirects to
/.auth/basicAuth/login?originalPath=auth/callback
- The #fragment (containing the auth code and state) is lost
- After entering the password again, the user lands on
/auth/callback without the auth code → authentication fails
Expected behavior
Either:
- Route rules with "allowedRoles": ["anonymous"] should bypass password protection for the specified routes,
allowing OAuth callback URLs to load without interception
- Or the password protection redirect should preserve the full URL including query parameters and fragments when
redirecting through /.auth/basicAuth/login
- Or the StaticWebAppsBasicAuthCookie should be sent on cross-origin navigations (using SameSite=None; Secure) so
that users who have already authenticated with the password are not prompted again on the OAuth callback
Device info (if applicable):
- OS: MacOS
- Browser: Chrome 147
Additional context
- This affects any SPA using OAuth redirect flows (MSAL.js, Auth0, etc.) when password protection is enabled
- The originalPath parameter in /.auth/basicAuth/login?originalPath=auth/callback only preserves the path — not
query parameters or fragments
- MSAL Browser intentionally uses response_mode=fragment for redirect flows (and does not allow overriding it to
query), so the fragment being stripped is a hard blocker
- IP allowlisting (networking.allowedIpRanges) works as an alternative access restriction without this issue, but is
not suitable for all scenarios (e.g., mobile app store review where reviewer IPs are unknown)
- This effectively makes password protection incompatible with any external OAuth/OIDC authentication
Describe the bug
When password protection is enabled on an Azure Static Web App, it is impossible to use any OAuth/OpenID Connect
redirect flow (e.g., Azure AD B2C, MSAL). The password protection layer intercepts the OAuth callback redirect and
strips the URL fragment (#code=...&state=...), making it impossible for the client-side library to complete the
token exchange.
Additionally, the StaticWebAppsBasicAuthCookie is not sent on cross-origin navigations back from the identity
provider (likely due to SameSite cookie policy), so even if the user has already authenticated with the password,
they are prompted again on the OAuth callback redirect — creating an infinite loop where the auth code is always
lost.
Route rules with "allowedRoles": ["anonymous"] do not bypass password protection, as the password layer runs before
route rules are evaluated.
To Reproduce
Steps to reproduce the behavior:
(e.g., /auth/callback)
/auth/callback#code=...&state=.../.auth/basicAuth/login?originalPath=auth/callback/auth/callbackwithout the auth code → authentication failsExpected behavior
Either:
allowing OAuth callback URLs to load without interception
redirecting through /.auth/basicAuth/login
that users who have already authenticated with the password are not prompted again on the OAuth callback
Device info (if applicable):
Additional context
query parameters or fragments
query), so the fragment being stripped is a hard blocker
not suitable for all scenarios (e.g., mobile app store review where reviewer IPs are unknown)