trails/docs/adr/0004-centralize-auth-completion.md
Ullrich Schäfer 7e22f1260b
Add unify-auth-completion architecture artifacts
Extract the post-verify orchestration shared across passkey
register-finish, passkey login-finish, magic-link verify-code, and
magic-link click-through into a single completeAuth function. Two
ADRs record the decision:

- ADR-0004: centralize web auth completion (record terms + create
  session + redirect) in apps/journal/app/lib/auth/completion.ts.
- ADR-0005: explicitly no AuthMethod polymorphism. Passkey + magic-
  link is the entire identity surface; OAuth2/PKCE is session
  transport, not a peer method. Recorded as a negative decision so
  future architecture passes don't re-suggest extracting the
  interface.

CONTEXT.md gains an Authentication section covering completeAuth, the
two methods, the OAuth2-as-transport distinction, and where the Terms
gate enforcement lives (root loader for web, requireApiUser for API
per the just-merged mobile-terms-gate).

OpenSpec change unify-auth-completion captures the proposal, design
(5 decisions including the negative-scope choices), spec delta on
authentication-methods, and 14 tasks. Implementation follows on this
branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 02:18:06 +02:00

1.4 KiB

Centralize web auth completion in completeAuth

Every successful web authentication today (passkey login, passkey register, magic-link code verify, magic-link click-through verify) inlines the same three-step orchestration in its route handler: record the accepted Terms version on registration, mint the session cookie, and redirect to returnTo (or /). With four call sites re-implementing the same sequence, a bug in any of them — a missed terms write, a wrong cookie option, an open-redirect-prone returnTo — must be fixed in four places, and a future fifth flow (e.g. an admin-impersonation login, a one-time recovery flow) re-derives the same boilerplate.

We extract the orchestration into a single function completeAuth({ userId, isNewRegistration, termsVersion?, request, returnTo? }) at apps/journal/app/lib/auth/completion.ts. Per-method identity verification (WebAuthn ceremony, magic-token consumption) stays in its own function and runs before completeAuth — the chokepoint deliberately knows nothing about how identity was proved. Terms enforcement (the gate) remains middleware (root loader for web, requireApiUser for API); completeAuth only records terms on registration. The OAuth-code-issuance flow at /oauth/authorize is not routed through completeAuth — it operates on an already-authenticated user and shares only the trailing redirect, not the full sequence (see ADR-0005 for the broader scope decision).