Skip to content

Steam Auth Flow

mdeguzis edited this page Jun 9, 2026 · 2 revisions

Steam Auth Flow

This page documents the current Steam sign-in flow used by the proton-pulse-web GitHub Pages site, along with the regression we hit after adding the login interstitial on April 16, 2026.

Scope

This is for the static site in proton-pulse-web, not the Decky plugin runtime.

Relevant files:

  • proton-pulse-web/js/lib/supabase-client.js
  • proton-pulse-web/auth.html
  • proton-pulse-web/js/auth/main.js
  • proton-pulse-web/js/profile/main.js
  • proton-pulse-web/supabase/functions/steam-callback/index.ts

Current Flow

  1. A logged-out user clicks Login with Steam from index.html, app.html, or profile.html.
  2. The site sends them to auth.html?returnTo=<current-page-url>.
  3. auth.js reads returnTo and calls SupaAuth.loginWithSteam(returnTo) when the user continues.
  4. supabase-client.js redirects to Steam OpenID with:
    • openid.return_to=<supabase edge function url>?returnTo=<original-page-url>
    • openid.realm=<supabase edge function url>
  5. Steam redirects to the Supabase Edge Function at functions/v1/steam-callback.
  6. The callback verifies the OpenID assertion with Steam, signs the user into Supabase, then redirects back to the site.
  7. The site receives #access_token=...&refresh_token=...&type=steam in the URL hash.
  8. supabase-client.js consumes that hash via _sb.auth.setSession(...), removes the hash from the URL, and only then lets getSession() and initial auth-state consumers proceed.

Regression We Hit

Symptom

Users saw:

  • {"code":"UNAUTHORIZED_NO_AUTH_HEADER","message":"Missing authorization header"}

and login sometimes appeared to land on the wrong page after the Steam round-trip.

What Changed

Commit 14e421b6f on April 16, 2026 added a Steam login interstitial and introduced buildLoginPageUrl(returnTo).

That change was fine on the entry side, but it left two gaps:

  1. returnTo was never preserved through the Edge Function callback.
  2. Session restoration from the callback hash was kicked off asynchronously, but nothing waited for it before early auth-dependent code called getSession() or subscribed to initial auth state.

Root Cause

There were two separate issues:

1. Lost redirect target

The interstitial correctly stored returnTo, but steam-callback/index.ts always redirected to SITE_URL root instead of the originating page.

2. Session timing race

consumeSessionFromHash() called setSession(...), but the rest of the client boot continued immediately.

That meant a protected request could happen before the Steam access token had actually been restored into the Supabase client session, producing a missing auth header on requests that expected a real bearer token.

Fix

The fix has three parts:

Client: normalize and preserve returnTo

supabase-client.js now:

  • normalizes returnTo to same-origin URLs only
  • adds that returnTo to the Edge Function callback URL
  • exposes the same normalized value through buildLoginPageUrl(...)

Client: wait for session restoration

supabase-client.js now stores the startup promise:

  • const _sessionReady = consumeSessionFromHash();

and then waits for it in:

  • getSession()
  • initial onStateChange(...) registration flow

That removes the startup race after the Steam callback returns with the auth hash.

Server: honor returnTo

supabase/functions/steam-callback/index.ts now:

  • reads returnTo from its own query string
  • validates that the parsed URL stays on the site origin
  • redirects there instead of always redirecting to SITE_URL

Deployment Caveat

This auth flow has two deployment surfaces:

GitHub Pages

These changes are needed for:

  • supabase-client.js
  • auth.html
  • auth.js
  • page shell files that launch the login flow

Publishing Pages updates alone is not enough to activate callback changes.

Supabase Edge Function

Changes in supabase/functions/steam-callback/index.ts only take effect after the function is redeployed to Supabase.

If the Pages files are updated but the Edge Function is not, the client can improve session timing, but callback redirect behavior will still reflect the old deployed function.

Operational Checklist

When changing Steam auth again:

  1. Verify buildLoginPageUrl(...), loginWithSteam(...), and steam-callback/index.ts all agree on how returnTo is passed.
  2. Keep callback redirects same-origin only.
  3. Ensure startup auth consumers wait until callback-hash session restoration completes.
  4. Deploy both:
    • GitHub Pages shell files
    • Supabase steam-callback Edge Function
  5. Test from at least two entry points:
    • home page top-bar login
    • profile.html signed-out login
  6. Confirm post-login return lands back on the page where sign-in started.

Known Good Expectation

After a successful Steam sign-in:

  • the user should return to the page they started from
  • the URL hash should be cleared after session restoration
  • SupaAuth.getSession() should return a valid session
  • authenticated requests should send a real bearer token, not fall back to an unauthenticated request path

Clone this wiki locally