refactor: standardize auth cookie configuration and improve logout reliability by explicitly expiring session tokens
This commit is contained in:
@@ -5,8 +5,7 @@ import { cookies } from 'next/headers';
|
||||
export async function clearAuthCookies() {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
// Delete all next-auth v5 cookie variants
|
||||
// Must specify path and secure options to match how they were set
|
||||
// All possible NextAuth cookie names (v4 + v5, secure + host prefixed)
|
||||
const cookieNames = [
|
||||
'authjs.session-token',
|
||||
'authjs.csrf-token',
|
||||
@@ -14,27 +13,46 @@ export async function clearAuthCookies() {
|
||||
'__Secure-authjs.session-token',
|
||||
'__Secure-authjs.csrf-token',
|
||||
'__Secure-authjs.callback-url',
|
||||
// next-auth v4 fallback names
|
||||
'next-auth.session-token',
|
||||
'next-auth.csrf-token',
|
||||
'next-auth.callback-url',
|
||||
'__Secure-next-auth.session-token',
|
||||
'__Secure-next-auth.csrf-token',
|
||||
'__Secure-next-auth.callback-url',
|
||||
// Host-prefixed variants (used when behind a proxy)
|
||||
'__Host-authjs.csrf-token',
|
||||
'__Host-next-auth.csrf-token',
|
||||
];
|
||||
|
||||
// Aggressively expire each cookie by setting empty value + maxAge: 0
|
||||
// This works better than .delete() in production behind load balancers
|
||||
for (const name of cookieNames) {
|
||||
// Delete with explicit options to ensure cookie is actually removed
|
||||
cookieStore.delete({
|
||||
name,
|
||||
path: '/',
|
||||
});
|
||||
try {
|
||||
cookieStore.set({
|
||||
name,
|
||||
value: '',
|
||||
path: '/',
|
||||
maxAge: 0,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'lax',
|
||||
});
|
||||
} catch {
|
||||
// Some Secure cookies cannot be set in non-HTTPS environments
|
||||
}
|
||||
// Also try non-secure version
|
||||
try {
|
||||
cookieStore.set({
|
||||
name,
|
||||
value: '',
|
||||
path: '/',
|
||||
maxAge: 0,
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Also try getting all cookies and deleting any auth-related ones
|
||||
// Sweep: any remaining cookies that look auth-related → expire them
|
||||
const allCookies = cookieStore.getAll();
|
||||
for (const cookie of allCookies) {
|
||||
if (
|
||||
@@ -42,7 +60,24 @@ export async function clearAuthCookies() {
|
||||
cookie.name.includes('next-auth') ||
|
||||
cookie.name.includes('session-token')
|
||||
) {
|
||||
cookieStore.delete({ name: cookie.name, path: '/' });
|
||||
try {
|
||||
cookieStore.set({
|
||||
name: cookie.name,
|
||||
value: '',
|
||||
path: '/',
|
||||
maxAge: 0,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'lax',
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
cookieStore.set({ name: cookie.name, value: '', path: '/', maxAge: 0 });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,25 +33,30 @@ export default function LogoutPage() {
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('user');
|
||||
|
||||
// Delete httpOnly cookies via server action FIRST
|
||||
// This ensures the session cookie is gone before signOut triggers session checks
|
||||
await clearAuthCookies();
|
||||
// Call NextAuth signOut — canonical way to clear session cookie
|
||||
// (uses the exact cookie name/attributes from cookies config in auth.ts)
|
||||
try {
|
||||
await signOut({ redirect: false });
|
||||
} catch {
|
||||
// Continue even if signOut fails
|
||||
}
|
||||
|
||||
// Client-side signOut to clear NextAuth client state
|
||||
await signOut({ redirect: false });
|
||||
// Belt-and-suspenders: server action to nuke any remaining auth cookies
|
||||
try {
|
||||
await clearAuthCookies();
|
||||
} catch {
|
||||
// Continue
|
||||
}
|
||||
|
||||
// Delete cookies again after signOut in case signOut recreated any
|
||||
await clearAuthCookies();
|
||||
|
||||
// Clear the logout flag before redirecting so it doesn't block API calls on public pages
|
||||
// Clear the logout flag before redirecting
|
||||
localStorage.removeItem('isLoggingOut');
|
||||
|
||||
// Redirect to login immediately
|
||||
window.location.href = '/';
|
||||
// Hard reload (replace, not push) so all in-memory state and cached responses are flushed
|
||||
window.location.replace('/');
|
||||
};
|
||||
|
||||
performLogout().catch(() => {
|
||||
window.location.href = '/';
|
||||
window.location.replace('/');
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user