The first trial of implementing login_hint with Azure Functions was done here: Azure Static Web Apps: Add login_hint with Functions.
This post shows how to achieve the same using an Azure Web App on Node.js, connected as the API backend to SWA.
Full source code: github.com/AndrewElans/azure-swa-loginhint-enabled
The Problem
Azure Static Web Apps drops the login_hint query parameter from the EasyAuth login flow. It never reaches the login.microsoftonline.com authorization endpoint.
Default EasyAuth Flow
- User navigates to:
https://swa.azurestaticapps.net/.auth/login/aad?login_hint=bing.whatman@contoso.com
- SWA redirects to its internal auth route, appending a nonce:
https://swa.azurestaticapps.net/.auth/login/aad
?post_login_redirect_uri=/.auth/complete
&staticWebAppsAuthNonce=aTakZLY...
- SWA redirects to Microsoft's authorize endpoint - without
login_hint:
https://login.microsoftonline.com/.../oauth2/v2.0/authorize
?response_type=code+id_token
&client_id=...
&scope=openid+profile+email
&prompt=select_account <-- forces account picker
&response_mode=form_post
...
The login_hint parameter we passed in Step 1 is gone. The user gets the account picker instead of a streamlined login.
The Fix
Intercept and replay the EasyAuth flow server-side using HTTP/2, injecting login_hint into the Microsoft authorize URL before redirecting the browser.
Stack
- Azure Static Web App
- Azure Web App (Node.js v24), linked as SWA's API backend
Why HTTP/2?
The EasyAuth redirect chain uses HTTP/2 pseudo-headers (:authority, :path). The middleware uses Node.js built-in http.createServer for the server and http2.connect for outbound requests to replay the flow correctly.
http.createServeris served ashttpsby default on Azure Web App.
Fixed Auth Flow
Here is the full flow, step by step.
Phase 1: Client-side redirect
- Client navigates to
https://swa.azurestaticapps.net - No active session (EasyAuth defaults to 8 hours) - SWA redirects to
/login/perstaticwebapp.config.json -
/login/redirects to/aad-redirect/, which readslogin_hintfromlocation.searchorlocalStorage - Client redirects to
/api/login-aad?user=bing.whatman
Phase 2: Server-side HTTP/2 flow
- Backend picks up
/api/login-aadand reads?userfromurlObj.searchParams - If
useris missing, fallback: redirect to the default EasyAuth route/.auth/login/aad - Send HTTP/2 request to
https://swa.azurestaticapps.net/.auth/login/aad- receive the redirect URI (location) andStaticWebAppsAuthContextCookie - Send HTTP/2 request to the received location (
/.auth/login/aad?post_login_redirect_uri=...&staticWebAppsAuthNonce=...) withStaticWebAppsAuthContextCookie- receive thelogin.microsoftonline.comURL as location, plus the nonce cookie
Phase 3: URL modification
- In the received Microsoft authorize URL:
- Replace
prompt=select_accountwithlogin_hint=bing.whatman@contoso.com - Replace
state=redir%3D%252F.auth%252Fcompletewithstate=redir%3D%252Fapi%252Flogin-aad-complete
- Replace
- Modify
StaticWebAppsAuthContextCookie: stripdomain=swa.azurestaticapps.net;(the backend has a different hostname - the browser will reject the cookie if this domain is present)
Phase 4: Browser redirect to Microsoft
- Redirect back to the browser with:
- Modified
StaticWebAppsAuthContextCookie - Nonce cookie
- Modified
login.microsoftonline.comlocation
- Modified
- Browser sets cookies and redirects to Microsoft login with
login_hint- no account picker
Phase 5: Auth completion
- Microsoft redirects back to the backend route
/api/login-aad-complete - Backend collects cookies (
StaticWebAppsAuthContextCookie,AppServiceAuthSession1,AppServiceAuthSession) and sends them via HTTP/2 tohttps://swa.azurestaticapps.net/.auth/complete- receivesStaticWebAppsAuthCookie - Backend redirects to SWA's
/login-aad-complete/, settingStaticWebAppsAuthCookieand deleting the nonce and context cookies -
/login-aad-complete/redirects to/perstaticwebapp.config.json
Key Detail: Cookie Domain Stripping
This is the non-obvious gotcha. When the backend replays the EasyAuth flow, the StaticWebAppsAuthContextCookie comes back with domain=swa.azurestaticapps.net. Since the backend runs on a different hostname, the browser will silently refuse to set this cookie.
The fix: strip the domain attribute from the Set-Cookie header before sending it to the browser.
// Before
StaticWebAppsAuthContextCookie=...; domain=swa.azurestaticapps.net; path=/; ...
// After
StaticWebAppsAuthContextCookie=...; path=/; ...
Fallback
If any step in the HTTP/2 flow fails, the middleware redirects to the standard EasyAuth route /.auth/login/aad. The user still logs in - they just get the account picker.
Source Code
Full implementation with detailed comments: github.com/AndrewElans/azure-swa-loginhint-enabled
Top comments (0)