2026-05-18-authjs-migration-decision

Mon May 18 2026 07:00:00 GMT+0700 (Western Indonesia Time)type/changelogtopic/authtopic/migration

2026-05-18 — Auth.js migration decision + Day-1 audit fixes

What

Strategic shift. Decided to migrate auth from the rolled-their-own JWT system to Auth.js v5 with Google OAuth + Credentials providers, rather than continuing to patch the existing system in place. Launch slipped from W21 to W23+ (~2 weeks). Full rationale in [[Projects/personal-finance-notion/decisions/adr-2026-05-18-authjs-migration|ADR 2026-05-18]].

Day-1 audit fixes landed in working tree (commits pending):

  • C4 — API error responses no longer leak internals in production. 5xx returns generic "Internal server error"; 4xx detail preserved for validation UX. logger.error replaces console.error. (src/lib/apiHelper.ts:42-44, 86-107)
  • H3 — Access-token blacklist on auto-rotate removed (the racy in-flight 401 path). Moot under Auth.js but a clean delta until file is replaced. (src/lib/auth.ts:345-353)
  • H4 — PWA api-reads StaleWhileRevalidate cache for /api/* GETs replaced with NetworkOnly. Closes the shared-device cross-user JSON leak. (next.config.mjs:25-35)
  • C3 (partial) — Verbatim "Email already exists" masked to a generic message. Full enumeration-safe signup ships in migration Phase B. (src/lib/actions/user.ts:123-132)

Why

The 2026-05-17 [[Projects/personal-finance-notion/context/audit-2026-05-17-auth|auth audit]] found that password reset (C1) and email verification (C2) — both required for launch — would need 4–5 days of custom work (email infra + token models + flows). Given that effort was unavoidable, folding it into a full Auth.js migration buys Google OAuth + CSRF/cookie defaults + reduced maintenance surface for ~5 extra days. Honest trade: Auth.js does not ship password reset or email verify for Credentials — those still need to be custom-built on top.

Findings remap (from audit)

Audit itemStatus after 2026-05-18
C1 Password resetDone — [[Projects/personal-finance-notion/backlog/done/p0-c1-password-reset|C1]]
C2 Email verificationSuperseded — Phase B
C3 Signup enumerationPartial mask landed; full fix Phase B
C4 API error leakageDone (commit pending)
H1 CSPPhase C
H2 Login timingPhase C
H3 Blacklist raceDone (commit pending); moot under Auth.js
H4 PWA cache leakDone (commit pending)
M1 Legacy cookie readsSuperseded by Auth.js
M2 Role fieldPhase C decision
M3 Dual JWT secretsSuperseded by Auth.js (single AUTH_SECRET)
M4 CSRF postureImproved by Auth.js defaults
M5 Logger stringifierPhase C
L1 Integration API tokensElevated to Phase C (blocking re-enable)
L2–L7Backlog unchanged

Migration phases

  • [[Projects/personal-finance-notion/backlog/done/p0-authjs-phase-a-foundation|Phase A · Foundation]] (shipped)
  • [[Projects/personal-finance-notion/backlog/done/p0-authjs-phase-b-features|Phase B · Features]] (shipped)
  • [[Projects/personal-finance-notion/backlog/done/p0-authjs-phase-c-hardening|Phase C · Integration API]] (shipped)
  • [[Projects/personal-finance-notion/backlog/done/p0-authjs-phase-d-pre-launch-hardening|Phase D · Hardening]] (shipped 2026-05-24)

Next

  • Stage + commit the Day-1 fixes in their own PR.
  • Branch for Phase A; start with adapter strategy decision (drop custom userId vs. write thin adapter).
  • Set up Google Cloud OAuth 2.0 client (Phase B can't start without it).