Skip to content

fix(site/src): stop blocking post-login redirect on CSRF-protected authcheck#26601

Draft
jakehwll wants to merge 1 commit into
mainfrom
jakehwll/devex-334-flake-test-e2e-premium-and-test-e2e
Draft

fix(site/src): stop blocking post-login redirect on CSRF-protected authcheck#26601
jakehwll wants to merge 1 commit into
mainfrom
jakehwll/devex-334-flake-test-e2e-premium-and-test-e2e

Conversation

@jakehwll

Copy link
Copy Markdown
Contributor

🤖 This PR was written by Coder Agents on behalf of Jake Howell.

The test-e2e / test-e2e-premium flake tracked in DEVEX-334 (internal#461) is two latent SPA bugs lined up on the post-login redirect path:

  1. loginFn in site/src/api/queries/users.ts fires POST /api/v2/authcheck in a Promise.all immediately after the login response sets the session cookie, which makes that authcheck CSRF-protected. Any failure there rejects the login mutation, so the location.href = sanitizeRedirect(redirectTo) hard reload in LoginPage never runs and the page stays on /login.
  2. X-CSRF-TOKEN was captured on the axios default headers exactly once, at module init, from <meta property="csrf-token">. If the bundle evaluated before the meta tag was populated (or a later document refetch rotated the csrf_token cookie) the cached header drifted from the cookie and every CSRF-protected POST 400'd.

Drop checkAuthorization from loginFn and let AuthProvider.permissionsQuery fetch authorization on the next render. The post-login hard reload re-runs that query against a fresh document anyway, so the only cost is one extra request on the dashboard load.

Replace the cached header with a per-request axios interceptor that reads the meta tag on every call, so the header always reflects whatever the browser most recently received. The module-init CSRF token not found warning is preserved.

Refs: DEVEX-334, coder/internal#461

Decision log
  • Considered exempting /api/v2/authcheck from CSRF on the server (mirroring /api/v2/users/first) and rejected it: authcheck is authoritative for permissions and exempting it would weaken the CSRF posture without addressing the broader header-staleness bug.
  • Considered keeping checkAuthorization inside loginFn but reordering it after the hard reload point. Rejected: same race window, and the hard reload already gets a fresh permissions fetch for free on the new document.
  • Considered swapping the meta-tag lookup for the csrf_token cookie, but it is HttpOnly, so the client cannot read it.
  • EmbedContext.tsx (VS Code embed flow) uses the same data.permissions pattern but authenticates via setSessionToken (header, not cookie), which exempts it from CSRF per coderd/httpmw/csrf.go. Out of scope.

…authcheck

The DEVEX-334 e2e flake is two latent bugs stacked on top of each other:

1. `loginFn` fires POST /api/v2/authcheck via Promise.all immediately
   after the login response sets the session cookie, which makes
   authcheck CSRF-protected. Any failure here rejects the login
   mutation and the `location.href` hard reload in LoginPage never
   runs, leaving the page on /login.

2. `X-CSRF-TOKEN` was cached on the axios default headers exactly once,
   at module init, from `<meta property="csrf-token">`. If the bundle
   evaluated before the meta tag was populated (or the cookie was
   rotated later by a document refetch), the cached header drifted
   from the `csrf_token` cookie and every CSRF-protected POST 400d.

Drop `checkAuthorization` from `loginFn` and let AuthProviders
`permissionsQuery` fetch it on the next render. The post-login hard
reload re-runs that query against a fresh document anyway, so this
only costs one extra request on the dashboard load.

Replace the cached header with a per-request axios interceptor that
re-reads the meta tag on every call. Module-init still surfaces the
"CSRF token not found" warning so misconfigured pages remain obvious.

Refs: DEVEX-334
@linear-code

linear-code Bot commented Jun 23, 2026

Copy link
Copy Markdown

DEVEX-334

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