Password protection breaks OAuth/OIDC redirect flows by stripping URL fragments and ignoring route-level anonymous access rules
- Dominant language
- No language data
- Stars
- 346
- Forks
- 67
- PR merge metrics
- No merged PRs in 30d
Description
**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:
1. Enable password protection on a Static Web App environment
2. 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)
3. Add a route rule to allow anonymous access to the callback path:
```
{
"routes": [
{
"route": "/auth/callback*",
"allowedRoles": ["anonymous"]
}
]
}
```
4. Visit the app → enter the SWA password → land on the login page
5. Click "Sign in" → get redirected to the identity provider (e.g., Azure AD B2C)
6. Authenticate successfully → identity provider redirects back to `/auth/callback#code=...&state=...`
7. SWA intercepts the redirect → redirects to `/.auth/basicAuth/login?originalPath=auth/callback`
8. The #fragment (containing the auth code and state) is lost
9. 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
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.