DEV Community

Andrew Elans
Andrew Elans

Posted on • Edited on

Azure Static Web Apps: Add login_hint with Node.js API Backend

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

  1. User navigates to:
https://swa.azurestaticapps.net/.auth/login/aad?login_hint=bing.whatman@contoso.com
Enter fullscreen mode Exit fullscreen mode
  1. 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...
Enter fullscreen mode Exit fullscreen mode
  1. 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
  ...
Enter fullscreen mode Exit fullscreen mode

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.createServer is served as https by default on Azure Web App.


Fixed Auth Flow

Here is the full flow, step by step.

Phase 1: Client-side redirect

  1. Client navigates to https://swa.azurestaticapps.net
  2. No active session (EasyAuth defaults to 8 hours) - SWA redirects to /login/ per staticwebapp.config.json
  3. /login/ redirects to /aad-redirect/, which reads login_hint from location.search or localStorage
  4. Client redirects to /api/login-aad?user=bing.whatman

Phase 2: Server-side HTTP/2 flow

  1. Backend picks up /api/login-aad and reads ?user from urlObj.searchParams
  2. If user is missing, fallback: redirect to the default EasyAuth route /.auth/login/aad
  3. Send HTTP/2 request to https://swa.azurestaticapps.net/.auth/login/aad - receive the redirect URI (location) and StaticWebAppsAuthContextCookie
  4. Send HTTP/2 request to the received location (/.auth/login/aad?post_login_redirect_uri=...&staticWebAppsAuthNonce=...) with StaticWebAppsAuthContextCookie - receive the login.microsoftonline.com URL as location, plus the nonce cookie

Phase 3: URL modification

  1. In the received Microsoft authorize URL:
    • Replace prompt=select_account with login_hint=bing.whatman@contoso.com
    • Replace state=redir%3D%252F.auth%252Fcomplete with state=redir%3D%252Fapi%252Flogin-aad-complete
  2. Modify StaticWebAppsAuthContextCookie: strip domain=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

  1. Redirect back to the browser with:
    • Modified StaticWebAppsAuthContextCookie
    • Nonce cookie
    • Modified login.microsoftonline.com location
  2. Browser sets cookies and redirects to Microsoft login with login_hint - no account picker

Phase 5: Auth completion

  1. Microsoft redirects back to the backend route /api/login-aad-complete
  2. Backend collects cookies (StaticWebAppsAuthContextCookie, AppServiceAuthSession1, AppServiceAuthSession) and sends them via HTTP/2 to https://swa.azurestaticapps.net/.auth/complete - receives StaticWebAppsAuthCookie
  3. Backend redirects to SWA's /login-aad-complete/, setting StaticWebAppsAuthCookie and deleting the nonce and context cookies
  4. /login-aad-complete/ redirects to / per staticwebapp.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=/; ...
Enter fullscreen mode Exit fullscreen mode

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)