Skip to content

fix(auth): restore AAD local emulator fallback and normalize openIdIssuer URL (#947)#995

Open
LongOddCode wants to merge 1 commit intoAzure:mainfrom
LongOddCode:developer/LongOddCode/fix-aad-custom-auth-947
Open

fix(auth): restore AAD local emulator fallback and normalize openIdIssuer URL (#947)#995
LongOddCode wants to merge 1 commit intoAzure:mainfrom
LongOddCode:developer/LongOddCode/fix-aad-custom-auth-947

Conversation

@LongOddCode
Copy link
Copy Markdown
Contributor

Problem

Two regressions in the custom-auth AAD flow were introduced in 2.0.3 by PR #905. Reported separately as #928, #941, and #947 — this PR fixes the common root causes.

Regression 1 — /.auth/login/aad returns 400 locally

Before 2.0.3, running the CLI against a staticwebapp.config.json that declared an AAD custom-auth provider would serve the local auth emulator at /.auth/login/aad when the referenced env vars were not set. That behaviour is what makes local dev ergonomic: a developer can reuse the production config without provisioning a tenant.

After 2.0.3, checkCustomAuthConfigFields hard-fails:

400 AAD_CLIENT_ID not found in env for 'aad' provider

…which breaks swa start for every user whose config targets AAD.

Regression 2 — /oauth2/v2.0 issuer URL fails discovery

The deployed SWA runtime accepts https://login.microsoftonline.com/<tenant>/oauth2/v2.0 as an alias for the canonical https://login.microsoftonline.com/<tenant>/v2.0. The CLI’s OIDC discovery (via openid-client) does not — it follows redirects from <issuer>/.well-known/openid-configuration and ends up in ERR_TOO_MANY_REDIRECTS. Users therefore need two different openIdIssuer values (one for local, one for prod) which is what #947 describes.

Root Cause

# Where What changed in #905
1 auth-login-provider-custom.tscheckCustomAuthConfigFields The emulator fallback path was removed. Every missing env var now produces a 400 with no code path to the local emulator.
2 openidHelper.tsOpenIdHelper ctor new URL(issuerUrl) is passed directly to client.discovery() without normalization, so a /oauth2/v2.0 suffix breaks discovery.

src/msha/auth/index.ts routes /.auth/login/aad exclusively to the custom-auth handler when isCustomAuth=true, so URL-level fallback is not possible — the delegation must happen in-process.

Fix

File Change
src/core/utils/openidHelper.ts Add export function normalizeOpenIdIssuer(url) that rewrites a trailing /oauth2/v2.0 to /v2.0. Wire it into the OpenIdHelper constructor before new URL(...). Covers login.microsoftonline.com, *.ciamlogin.com, and Entra custom URL domains.
src/msha/auth/routes/auth-login-provider-custom.ts Add shouldFallbackToAadEmulator(providerName, customAuth) — returns true only when provider is aad, the config declares both setting names, and at least one referenced env var is unset (i.e. clearly a local-dev case). Delegate to authLoginProviderEmulator in httpTrigger before checkCustomAuthConfigFields.

Design notes:

  • checkCustomAuthConfigFields is unchanged. It is also used by auth-login-provider-callback.ts, which must keep strict 400 semantics — a callback with a real OAuth code has no legitimate reason to fall back to the emulator. Placing the pre-check in the httpTrigger caller avoids coupling the two code paths.
  • Config errors still surface as 400. If clientIdSettingName or clientSecretSettingName is missing from the config file entirely (as opposed to referenced-but-unset env vars), we fall through to the existing 400 response. Test case (e) covers this.

Testing

All new and existing tests pass against Node 20.18.0.

Test Files  34 passed (34)
     Tests  502 passed | 14 skipped (516)

New tests (17):

Spec file Cases
src/core/utils/openidHelper.spec.ts 12 — normalizeOpenIdIssuer correctness across tenant-id / common / organizations, ciamlogin.com, custom Entra domains, canonical URLs preserved, trailing slash preserved, unrelated URLs untouched
src/msha/auth/routes/auth-login-provider-custom.spec.ts 5 — emulator fallback when clientId unset / secret unset / both unset; real-auth path when both set; still-400 when config field is missing

Manual repro of #947 locally:

  1. staticwebapp.config.json with openIdIssuer = https://login.microsoftonline.com/<tenant>/oauth2/v2.0 and env vars unset.
  2. Before: GET /.auth/login/aad → 400 AAD_CLIENT_ID not found in env.
  3. After: GET /.auth/login/aad → local auth emulator HTML (same as pre-2.0.3).

References

…suer URL (Azure#947)

Fixes two regressions in the custom-auth AAD flow introduced by Azure#905 in 2.0.3:

1. `/.auth/login/aad` now returns a 400 "AAD_CLIENT_ID not found in env" in local dev, even though the pre-2.0.3 behaviour was to serve the local auth emulator. Restore the fallback: when the config references env vars but those env vars are unset, delegate to the SWA local auth emulator instead of hard-failing. Missing config fields still 400 as before.

2. An `openIdIssuer` of `https://login.microsoftonline.com/<tenant>/oauth2/v2.0` (accepted by the deployed SWA runtime) caused ERR_TOO_MANY_REDIRECTS during CLI OIDC discovery. Normalize the URL to the canonical `/v2.0` form in `OpenIdHelper` so one `staticwebapp.config.json` works both locally and deployed.

Adds 17 unit tests covering both regressions. All 502 existing tests still pass.

Refs: Azure#928, Azure#941, Azure#947, PR Azure#905
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AzureActiveDirectory (AAD) custom auth provider is broken in 2.0.3

1 participant