Skip to content

fix: stop stale CSRF token 419s on long-lived mobile tabs#327

Merged
gregqualls merged 4 commits into
mainfrom
fix/csrf-stale-token-mobile
May 29, 2026
Merged

fix: stop stale CSRF token 419s on long-lived mobile tabs#327
gregqualls merged 4 commits into
mainfrom
fix/csrf-stale-token-mobile

Conversation

@gregqualls

@gregqualls gregqualls commented May 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Mobile users hit a CSRF error (419) on every write once a backgrounded SPA tab outlived the 120-minute session. Reads kept working (GET skips CSRF), so the app looked fine until you tried to change something.
  • Root cause: the API client read its CSRF token from the <meta name="csrf-token"> tag, captured once at page load. When the server session rotated its token, the SPA kept sending the stale one. Phones freeze tabs far longer than desktop, so they bore the brunt.
  • Fix in resources/js/services/api.js: read the XSRF-TOKEN cookie Sanctum keeps in sync with the session (sent as X-XSRF-TOKEN) instead of the frozen meta tag, plus a response interceptor that refreshes /sanctum/csrf-cookie and replays the request once on a 419.
  • Also bumped vulnerable Symfony transitive deps (http-foundation, http-kernel, mailer, mime, polyfill-intl-idn, routing) to 7.4.12/7.4.13, clearing 8 composer audit advisories. Patch bumps within the 7.4.x line, no app code changes.

Test plan

  • Log in on a phone, leave the tab backgrounded past the session lifetime, return and edit something: the write succeeds (no 419).
  • Normal happy-path writes (create/edit task, vault item, recipe) still succeed on desktop and mobile.
  • composer audit reports 0 advisories.
  • Full quality gate green (already passing locally).

Checklist

  • I've tested this locally (quality gate green; happy-path write not exercised in-browser, see note)
  • Mobile-first — works on 375px screens (no visual change)
  • Dark mode — no visual change
  • No new secrets or credentials committed
  • API changes documented (no API contract change; client CSRF transport only)

Note: the session-expiry recovery path can't be exercised in a quick local preview (needs a real 2-hour session timeout + full backend). The change uses the canonical Sanctum SPA cookie mechanism, so confidence is high, but worth confirming the happy-path write on the preview env.

Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com

Session Handoff (auto-updated by /handoff)

Updated: 2026-05-29
Branch: fix/csrf-stale-token-mobile
Last commit: 14b2217 chore: correct version bump to v1.10.1

What was done this session

  • Diagnosed the mobile "CSRF error on every change" report: stale CSRF token on long-lived backgrounded SPA tabs (session rotates after 120 min, frozen meta-tag token no longer matches).
  • Fixed resources/js/services/api.js to read the XSRF-TOKEN cookie (sent as X-XSRF-TOKEN) and added a 419 response interceptor that refreshes /sanctum/csrf-cookie and replays once.
  • Bumped vulnerable Symfony transitive deps to 7.4.12/7.4.13, clearing 8 composer audit advisories that were failing the gate.
  • Bumped app version to v1.10.1 (patch: bug fix + security dep bump, no new features) and added the CHANGELOG entry.

Quality state

  • Quality gate green this session: PHP syntax, Pint, Larastan, PHPUnit, Vite, ESLint all PASS; Dependency Audit PASS (0 vulnerabilities after the bump). Lone WARN is pre-existing accessibility (10 items), not a hard gate.
  • CI: passed. Preview env: verified good by Greg.

What's next

  • Merge via /merge (squash). It tags v1.10.1 after the squash commit lands on main.
  • Optional after merge: confirm the happy-path write on production once deployed.

Blockers / gotchas

  • The actual session-expiry recovery path can't be exercised in a quick local preview (needs a real 2-hour timeout + full backend). Mechanism is the canonical Sanctum SPA cookie flow, so confidence is high; preview confirmed normal writes still work.
  • The now-unused <meta name="csrf-token"> tag in app.blade.php was left in place (harmless fallback); remove later if desired.

Open questions

  • None.

gregqualls and others added 4 commits May 29, 2026 12:04
Long-lived mobile SPA tabs sent a CSRF token frozen at page load (from
the meta tag). Once the 120-minute session rotated its token, every
write returned 419 while reads kept working, so the app looked fine
until you tried to change something.

Read the XSRF-TOKEN cookie Sanctum keeps in sync with the session and
send it as X-XSRF-TOKEN, and add a response interceptor that refreshes
the CSRF cookie and replays the request once on a 419.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
http-foundation, http-kernel, mailer, mime, polyfill-intl-idn, and
routing to 7.4.12/7.4.13. Clears 8 advisories (SSRF, header injection,
URL generation bypasses). Patch bumps within the 7.4.x line, no app
code changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Patch, not minor: this release is a bug fix (mobile CSRF) plus a
security dependency bump, no new features.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@gregqualls gregqualls merged commit 89dadd9 into main May 29, 2026
7 checks passed
@gregqualls gregqualls deleted the fix/csrf-stale-token-mobile branch May 29, 2026 11:26
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