diff --git a/src/app/logout/actions.ts b/src/app/logout/actions.ts index bc09ca2..baf4192 100644 --- a/src/app/logout/actions.ts +++ b/src/app/logout/actions.ts @@ -1,21 +1,23 @@ 'use server'; import { cookies } from 'next/headers'; -import { redirect } from 'next/navigation'; -export async function logoutAction() { - // 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. +export async function clearAuthCookies() { const cookieStore = await cookies(); - // Delete all possible next-auth v5 cookie names - cookieStore.delete('authjs.session-token'); - cookieStore.delete('authjs.csrf-token'); - cookieStore.delete('authjs.callback-url'); - cookieStore.delete('__Secure-authjs.session-token'); - cookieStore.delete('__Secure-authjs.csrf-token'); - cookieStore.delete('__Secure-authjs.callback-url'); + // Delete all next-auth v5 cookie variants + const cookieNames = [ + 'authjs.session-token', + 'authjs.csrf-token', + 'authjs.callback-url', + '__Secure-authjs.session-token', + '__Secure-authjs.csrf-token', + '__Secure-authjs.callback-url', + ]; - redirect('/login'); + for (const name of cookieNames) { + cookieStore.delete(name); + } + + return { success: true }; } diff --git a/src/app/logout/page.tsx b/src/app/logout/page.tsx index 28d8bb2..4291e9d 100644 --- a/src/app/logout/page.tsx +++ b/src/app/logout/page.tsx @@ -1,7 +1,8 @@ 'use client'; import { useEffect, useRef } from 'react'; -import { logoutAction } from './actions'; +import { clearAuthCookies } from './actions'; +import { signOut } from 'next-auth/react'; export default function LogoutPage() { const hasStarted = useRef(false); @@ -11,79 +12,26 @@ export default function LogoutPage() { hasStarted.current = true; const performLogout = async () => { - // Set logout flag to prevent TokenSync/PresenceProvider from restoring tokens + // Clear localStorage 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('refreshToken'); localStorage.removeItem('user'); - // Manually clear non-httpOnly next-auth cookies as fallback - const cookieNames = [ - 'authjs.session-token', - '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`; - }); + await signOut({ redirect: false }) + // Delete httpOnly cookies via server action (Next.js cookies() API) + await clearAuthCookies(); - // Use server action to properly clear the httpOnly session cookie. - // signOut() internally calls redirect() which throws NEXT_REDIRECT — - // Next.js handles this automatically. We should NOT catch that error. - // 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 + setTimeout(() => { + // Redirect to login + window.location.reload() window.location.href = '/login'; - } + }, 3000) }; - performLogout(); + performLogout().catch(() => { + window.location.href = '/login'; + }); }, []); return ( diff --git a/src/components/layout/CommonHeader.tsx b/src/components/layout/CommonHeader.tsx index 20aae1b..6db4381 100644 --- a/src/components/layout/CommonHeader.tsx +++ b/src/components/layout/CommonHeader.tsx @@ -280,12 +280,6 @@ export function CommonHeader() {