This repository was archived by the owner on Oct 5, 2026. It is now read-only.
Repository navigation
fix(auth-core): make the fixation revoke fire on the form_post path (#36) - #48
Merged
Merged
Conversation
) `issue` revokes the session the browser was carrying before minting a new one, and it identified that session by the raw cookie the request presented. On Apple's `form_post` callback the session cookie is `SameSite=Lax` and is simply not sent with a cross-site POST, so `presented_cookie` was always `None` and the revoke could never fire for that provider. #44 documented the gap; this closes it. Option 1 from the issue, which it called probably right: - `Login` gains `presented_session_id`, used when `presented_cookie` is `None`. The cookie stays the stronger evidence and wins when both are present. - `/start` — which IS same-site, so the cookie arrives there — seals the session **id** into the signed flow next to the user id it already sealed, and the callback hands it back. The id, not the value, and that is the point of the design: the value is a bearer credential and a second copy of it inside another cookie widens its exposure for nothing, while an id is useless without the row. Adding the field to `Login` deliberately breaks all thirteen call sites rather than defaulting: whether a login can name the session it replaces is a decision each path should make. The four same-site paths (password login and registration, magic-link consume, passkey login) pass `None` and say why — their cookie arrives. One test, on the path that actually reaches `issue`: two Apple sign-ins, the first session carried into the second `/start`, asserting the old cookie no longer validates and its row is marked revoked rather than merely orphaned. Verified it fails with either half of the chain broken — the core ignoring the id, and `/start` not sealing it. Meta gets the same wiring for symmetry, but its callback is a GET redirect, so its cookie already arrives and the cookie branch wins there; this is a fallback that should not normally fire. ADR 0102 gains an update note. The paragraph describing the gap is left intact: it is an accurate record of the state between #35 and now. 397 tests, clippy -D warnings, fmt, wasm32 workspace build. Closes #36. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8K9TBtmnf29dV6uEP5oM3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #36. #44 documented this gap; this closes it, using option 1 from the issue — the one it called probably right.
The defect
issuerevokes the session the browser was carrying before minting a new one, and it identified that session by the raw cookie the request presented:On Apple's
form_postcallback the session cookie isSameSite=Laxand is simply not sent with a cross-site POST.presented_cookiewas therefore alwaysNonethere, and the revoke could never fire for that provider — the superseded row stayed live server-side until it expired.The fix
Logingainspresented_session_id, used whenpresented_cookieisNone. The cookie stays the stronger evidence and wins when both are present./start— which is same-site, so the cookie arrives there — seals the session id into the signed flow next to the user id it already sealed. The callback hands it back.The id, not the value, and that is the design rather than an implementation detail: the value is a bearer credential, and a second copy of it inside another cookie widens its exposure for nothing. An id is useless without the row.
Breaking every call site was deliberate
Adding the field breaks all thirteen
Login { .. }initializers rather than defaulting toNone. Whether a login can name the session it replaces is a decision each path should make, and the compiler now forces it. The four same-site production paths — password login and registration, magic-link consume, passkey login — passNonewith a one-line reason: their cookie arrives, so the stronger branch already handles them.The test, and both ways it fails
My first attempt at this test passed vacuously in the wrong direction: I built a scenario that returned
200 OKwithout ever reachingissue, because the linking rules answered with a confirm-link page. Rewritten on the path that actually issues — two Apple sign-ins, the first session carried into the second/start— it asserts the old cookie no longer validates and that its row is markedrevoked_atrather than merely orphaned.I then broke each half of the chain and confirmed it catches both:
issueignorespresented_session_id/startstops sealing the idMeta
Gets the same wiring for symmetry, but its callback is a GET redirect, so its session cookie already arrives and the cookie branch wins. This is a fallback there that should not normally fire — I have not claimed it as a fix for Meta.
ADR 0102
Gains a dated update note. The paragraph describing the gap is left intact: it is an accurate record of the state between #35 and now, and rewriting it would erase why the decision was deferred.
397 tests, clippy
--all-targets --all-features -D warnings, fmt, wasm32 workspace build.🤖 Generated with Claude Code
https://claude.ai/code/session_01V8K9TBtmnf29dV6uEP5oM3