Skip to content

Password protection breaks OAuth/OIDC redirect flows by stripping URL fragments and ignoring route-level anonymous access rules #1737

@mrzslr-pro

Description

@mrzslr-pro

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"]
    }
  ]
}
  1. Visit the app → enter the SWA password → land on the login page
  2. Click "Sign in" → get redirected to the identity provider (e.g., Azure AD B2C)
  3. Authenticate successfully → identity provider redirects back to /auth/callback#code=...&state=...
  4. SWA intercepts the redirect → redirects to /.auth/basicAuth/login?originalPath=auth/callback
  5. The #fragment (containing the auth code and state) is lost
  6. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions