#285 Fix Sign-Out Returns 403#289
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · needs revision
1 open — 1 🟠 Medium (see inline)
eb1c473 to
1706719
Compare
Revision — Cycle 1fixed R1-M1 · 1706719 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · needs revision
1 open — 1 🟠 Medium (see inline)
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · needs revision
3 open — 1 🟡 Low, 2 ⚪ Nit (see inline)
Note: a stale review (Cycle 1 draft) was posted immediately before this one with an incorrect finding (R1-M1 claiming missing auth check — disregard it;
getCurrentUser()is present at line 14 ofprisma/actions/auth.ts). This review supersedes it.
Replace client-side authClient.signOut() with a signOutUser() server action that proxies to authServer.signOut() via next/headers, removing the browser SameSite/CSRF fragility that caused the 403. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1706719 to
e0936ed
Compare
Revision — Cycle 1fixed R1-M1, R1-L1, R1-N2, R1-N1 · e0936ed |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 4 · needs revision
1 open — 1 🟠 Medium (see inline)
| // displays regardless of which path (redirect or error) completes first. | ||
| toast.success('Signed out.'); | ||
| const result = await signOutUser(); | ||
| if (isError(result)) { |
There was a problem hiding this comment.
R4-M1 🟠 Medium — toast.success('Signed out.') fires unconditionally before the action is called, so on the error path the user sees a success toast immediately followed by an error toast — contradictory feedback. ENGINEERING.md §4 requires a success toast for success and a specific error toast for { error }; showing both simultaneously violates that model. The rationale in the comment (fires before server redirect) is real but the fix is wrong: redirect() in a server action sends a redirect response to the client after the await resolves, so a toast fired after await signOutUser() on the non-error branch would also display before navigation. Fix: const result = await signOutUser(); if (isError(result)) { toast.error(result.error); } else { toast.success('Signed out.'); }.
Closes #285
Summary
authClient.signOut()(browser-side) POSTs to/api/auth/sign-out, which Neon Auth's upstream better-auth rejected due to CSRF/SameSite protection.signOutUser()server action that callsauthServer.signOut()server-side, forwarding the session cookie vianext/headers— bypassing browser origin/CSRF constraints entirely.Changes
prisma/actions/auth.ts(new) —signOutUser()server action: callsauthServer.signOut(), returns{ error }on upstream failure, revalidates layout cache and redirects to/loginon success.components/layouts/user-menu.tsx— replace the real-authauthClient.signOut()try/catch withsignOutUser(); drop the now-unusedauthClientimport anduseRouterhook; keep the bypass branch (logoutBypassUser()) unchanged.Testing plan
/loginwith no error toast and session cleared (revisiting a gated route redirects to login).NEON_AUTH_BASE_URLto an invalid value) and confirm the action returns the error toast and the menu re-enables without crashing or redirecting./login/bypass(unchanged path).authClient.signOutreferences remain in the codebase.Automated checks
prettier:check— passeslint:check— passtsc:check— passNotes
If the 403 persists in production after this change (server-side path still hits the upstream), the next investigation step is verifying the deployment origin is registered as a trusted origin in the Neon Auth dashboard — a config step outside this repo. The server-side path removes the most common cause (browser SameSite/CSRF fragility) and aligns with the repo's "mutations are server actions, no client fetching" rule.