refactor: streamline logout process by integrating next-auth's signOut and a dedicated server action for cookie management.

This commit is contained in:
pradeepkumar
2026-03-10 11:49:33 +05:30
parent c7f1b89203
commit 6055160b30
4 changed files with 30 additions and 91 deletions

View File

@@ -1,21 +1,23 @@
'use server'; 'use server';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export async function logoutAction() { export async function clearAuthCookies() {
// Force-delete all next-auth session cookies directly.
// signOut() from next-auth was NOT clearing the httpOnly cookie reliably,
// so we delete them explicitly using the cookies() API.
const cookieStore = await cookies(); const cookieStore = await cookies();
// Delete all possible next-auth v5 cookie names // Delete all next-auth v5 cookie variants
cookieStore.delete('authjs.session-token'); const cookieNames = [
cookieStore.delete('authjs.csrf-token'); 'authjs.session-token',
cookieStore.delete('authjs.callback-url'); 'authjs.csrf-token',
cookieStore.delete('__Secure-authjs.session-token'); 'authjs.callback-url',
cookieStore.delete('__Secure-authjs.csrf-token'); '__Secure-authjs.session-token',
cookieStore.delete('__Secure-authjs.callback-url'); '__Secure-authjs.csrf-token',
'__Secure-authjs.callback-url',
];
redirect('/login'); for (const name of cookieNames) {
cookieStore.delete(name);
}
return { success: true };
} }

View File

@@ -1,7 +1,8 @@
'use client'; 'use client';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { logoutAction } from './actions'; import { clearAuthCookies } from './actions';
import { signOut } from 'next-auth/react';
export default function LogoutPage() { export default function LogoutPage() {
const hasStarted = useRef(false); const hasStarted = useRef(false);
@@ -11,79 +12,26 @@ export default function LogoutPage() {
hasStarted.current = true; hasStarted.current = true;
const performLogout = async () => { const performLogout = async () => {
// Set logout flag to prevent TokenSync/PresenceProvider from restoring tokens // Clear localStorage
localStorage.setItem('isLoggingOut', 'true'); localStorage.setItem('isLoggingOut', 'true');
// Invalidate refresh token on the backend (best-effort)
const refreshToken = localStorage.getItem('refreshToken');
const accessToken = localStorage.getItem('accessToken');
if (refreshToken) {
try {
await fetch(
`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api/v1'}/auth/logout`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
},
body: JSON.stringify({ refreshToken }),
}
);
} catch {
// Best-effort — don't block logout if backend call fails
}
}
// Clear all localStorage auth data
localStorage.removeItem('accessToken'); localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken'); localStorage.removeItem('refreshToken');
localStorage.removeItem('user'); localStorage.removeItem('user');
// Manually clear non-httpOnly next-auth cookies as fallback await signOut({ redirect: false })
const cookieNames = [ // Delete httpOnly cookies via server action (Next.js cookies() API)
'authjs.session-token', await clearAuthCookies();
'authjs.callback-url',
'authjs.csrf-token',
'__Secure-authjs.session-token',
'__Secure-authjs.callback-url',
'__Secure-authjs.csrf-token',
'next-auth.session-token',
'next-auth.callback-url',
'next-auth.csrf-token',
'__Secure-next-auth.session-token',
];
cookieNames.forEach((name) => {
document.cookie = `${name}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
document.cookie = `${name}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure`;
});
// Use server action to properly clear the httpOnly session cookie. setTimeout(() => {
// signOut() internally calls redirect() which throws NEXT_REDIRECT — // Redirect to login
// Next.js handles this automatically. We should NOT catch that error. window.location.reload()
// Only use fallback if the action truly fails (not a redirect throw).
try {
await logoutAction();
} catch (error: unknown) {
// In Next.js App Router, redirect() throws an error with digest containing "NEXT_REDIRECT".
// This is normal behavior — let it propagate so Next.js handles the redirect.
const isRedirectError =
error instanceof Error &&
'digest' in error &&
typeof (error as { digest?: string }).digest === 'string' &&
(error as { digest: string }).digest.includes('NEXT_REDIRECT');
if (isRedirectError) {
// Re-throw so Next.js handles the redirect properly
throw error;
}
// Genuine error — use full page navigation as fallback
window.location.href = '/login'; window.location.href = '/login';
} }, 3000)
}; };
performLogout(); performLogout().catch(() => {
window.location.href = '/login';
});
}, []); }, []);
return ( return (

View File

@@ -280,12 +280,6 @@ export function CommonHeader() {
<button <button
onClick={() => { onClick={() => {
setShowProfileMenu(false); setShowProfileMenu(false);
// Set logout flag FIRST so TokenSync and PresenceProvider
// don't restore tokens during the logout navigation
localStorage.setItem('isLoggingOut', 'true');
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('user');
window.location.href = '/logout'; window.location.href = '/logout';
}} }}
className="w-full flex items-center gap-3 px-4 py-3 hover:bg-black/5 transition-colors" className="w-full flex items-center gap-3 px-4 py-3 hover:bg-black/5 transition-colors"

View File

@@ -34,17 +34,12 @@ export const resetLogoutState = () => {
isLoggingOut = false; isLoggingOut = false;
}; };
// Force logout: clear all tokens and redirect to logout page // Force logout: redirect to /logout page which handles everything
const forceLogout = () => { const forceLogout = () => {
if (isLoggingOut) return; // Prevent multiple redirects if (isLoggingOut) return;
isLoggingOut = true; isLoggingOut = true;
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
// Set flag first so TokenSync/PresenceProvider don't restore tokens
localStorage.setItem('isLoggingOut', 'true'); localStorage.setItem('isLoggingOut', 'true');
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('user');
// Redirect to /logout page which properly clears NextAuth session
window.location.href = '/logout'; window.location.href = '/logout';
} }
}; };