Skip to content

fix(web): redirect to sign-in on 401 instead of failing silently#78

Merged
steve-krisjanovs merged 1 commit into
mainfrom
fix/client-auth-redirect
Jul 1, 2026
Merged

fix(web): redirect to sign-in on 401 instead of failing silently#78
steve-krisjanovs merged 1 commit into
mainfrom
fix/client-auth-redirect

Conversation

@steve-krisjanovs

Copy link
Copy Markdown
Contributor

Summary

Follow-up to fix/loopback-auth-bypass (#77). That fix made 401 a real, reachable response for the picker's own client JS for the first time — a request from a trusted-looking client essentially never got one before, so nothing in the browser code had to handle it.

  • The poll loop (GET /api/sessions every 3s) just caught the resulting error and flipped the status dot to "offline" forever — no indication to sign in, no way to get there.
  • The session-attach page's WS reconnect loop had the same problem, worse: browsers don't expose a rejected WS handshake's HTTP status at all.

Fix

  • Global fetch() wrapper in both the picker's script and the session-attach page's script: any 401 redirects to /login?returnTo=<path> instead of the caller's existing catch block just showing a generic failure.
  • The WS reconnect loop's onclose handler now also fires a throwaway authed fetch('/api/sessions') alongside scheduling the reconnect, so an unauthorized attach surfaces the same redirect within one cycle instead of retrying forever.

Test plan

  • npm run typecheck / npm run build clean
  • Loaded the picker with a valid v2 session cookie, cleared the cookie mid-session (simulating expiry while a tab stays open) — confirmed the next poll cycle (~3s) navigated to /login?returnTo=%2F with the correct page title, instead of sitting on "offline"

🤖 Generated with Claude Code

https://claude.ai/code/session_01Au8T9RPCb6wbgiEecQrBfZ

Follow-up to fix/loopback-auth-bypass. That fix made 401 a real, reachable
response for the picker's own client JS for the first time — before it,
a request from a trusted-looking client essentially never got a 401, so
nothing in the browser code had to handle it. Two client-side gaps as a
result:

- The picker's poll loop (GET /api/sessions every 3s) just caught the
  resulting error and flipped the status dot to "offline" forever, with
  no indication the real problem is "sign in again" and no way to get
  there from the page.
- The session-attach page's WS reconnect loop had the same problem, but
  worse: browsers don't expose a rejected WebSocket handshake's HTTP
  status at all, so there's no way to even detect "this failed because
  of auth" from the WS event alone.

Fix: install a global fetch() wrapper in both the picker's script and the
session-attach page's script — any 401 redirects to
/login?returnTo=<path> instead of letting the caller's existing catch
block just show a generic failure. For the WS case, the reconnect loop's
onclose handler now also fires a throwaway authed fetch
(GET /api/sessions) alongside scheduling the reconnect; if the session is
actually invalid, that fetch 401s and the wrapper redirects the whole
page, tearing down the doomed reconnect loop within one cycle instead of
retrying forever.

Verified live: loaded the picker with a valid v2 session cookie, cleared
the cookie mid-session (simulating expiry/revocation while a tab stays
open), and confirmed the next poll cycle (~3s) navigated the browser to
/login?returnTo=%2F with the correct page title, rather than sitting on
"offline".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Au8T9RPCb6wbgiEecQrBfZ
@steve-krisjanovs steve-krisjanovs merged commit 216b870 into main Jul 1, 2026
1 check passed
@steve-krisjanovs steve-krisjanovs deleted the fix/client-auth-redirect branch July 1, 2026 14:51
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.

1 participant