Skip to content

Commit eb1c473

Browse files
b-at-neuclaude
andcommitted
#285 fix sign-out by moving it to a server action
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>
1 parent 76119ab commit eb1c473

2 files changed

Lines changed: 25 additions & 10 deletions

File tree

components/layouts/user-menu.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import { useTheme } from 'next-themes';
44
import Link from 'next/link';
5-
import { useRouter } from 'next/navigation';
65
import { useTransition } from 'react';
76

87
import {
@@ -16,9 +15,9 @@ import {
1615
} from 'lucide-react';
1716
import { toast } from 'sonner';
1817

18+
import { signOutUser } from '@/prisma/actions/auth';
1919
import { logoutBypassUser } from '@/prisma/services/dev-bypass';
2020

21-
import { authClient } from '@/lib/auth/client';
2221
import type { NavIdentity } from '@/lib/types';
2322

2423
import {
@@ -57,7 +56,6 @@ export function UserMenu({
5756
const { name, email, roleLabel, isBypass } = identity;
5857
const displayName = name ?? email;
5958
const [pending, startTransition] = useTransition();
60-
const router = useRouter();
6159
// next-themes is loaded with { ssr: false }; `theme` is undefined on first
6260
// render before the provider resolves. Default to 'system' to match
6361
// defaultTheme so the radio shows a selection without a flash.
@@ -74,14 +72,11 @@ export function UserMenu({
7472
await logoutBypassUser();
7573
return;
7674
}
77-
try {
78-
await authClient.signOut();
79-
toast.success('Signed out.');
80-
router.push('/login');
81-
router.refresh();
82-
} catch {
83-
toast.error('Could not sign out. Please try again.');
75+
const result = await signOutUser();
76+
if (result && 'error' in result) {
77+
toast.error(result.error);
8478
}
79+
// On success the server action redirects to /login — no client navigation needed.
8580
});
8681
}
8782

prisma/actions/auth.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use server';
2+
3+
import { revalidatePath } from 'next/cache';
4+
import { redirect } from 'next/navigation';
5+
6+
import { authServer } from '@/lib/auth/server';
7+
import type { ErrorType } from '@/lib/utils';
8+
9+
// Signs out the current real (non-bypass) Neon Auth session.
10+
// Sends the sign-out request server-side via authServer, which forwards the
11+
// session cookie through next/headers — avoiding the browser SameSite/CSRF
12+
// fragility that caused the 403 when sign-out was driven client-side.
13+
export async function signOutUser(): Promise<ErrorType | void> {
14+
const result = await authServer.signOut();
15+
16+
if (result.error) return { error: 'Could not sign out. Please try again.' };
17+
18+
revalidatePath('/', 'layout');
19+
redirect('/login');
20+
}

0 commit comments

Comments
 (0)