From 69eff70fbbf0bb00c81392501f01a3206b792e53 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Tue, 10 Mar 2026 00:14:43 +0530 Subject: [PATCH] feat: Implement a server-side logout action and enhance client-side logout with backend token invalidation and comprehensive local storage/cookie clearing. --- src/app/logout/actions.ts | 7 ++++ src/app/logout/page.tsx | 72 ++++++++++++++++++++++++++++++++------- 2 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 src/app/logout/actions.ts diff --git a/src/app/logout/actions.ts b/src/app/logout/actions.ts new file mode 100644 index 0000000..06510c5 --- /dev/null +++ b/src/app/logout/actions.ts @@ -0,0 +1,7 @@ +'use server'; + +import { signOut } from '@/auth'; + +export async function logoutAction() { + await signOut({ redirectTo: '/login' }); +} diff --git a/src/app/logout/page.tsx b/src/app/logout/page.tsx index 9714831..10cc0c3 100644 --- a/src/app/logout/page.tsx +++ b/src/app/logout/page.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useRef } from 'react'; -import { signOut } from 'next-auth/react'; +import { logoutAction } from './actions'; export default function LogoutPage() { const hasStarted = useRef(false); @@ -10,19 +10,67 @@ export default function LogoutPage() { if (hasStarted.current) return; hasStarted.current = true; - // Set logout flag to prevent TokenSync/PresenceProvider from restoring tokens - localStorage.setItem('isLoggingOut', 'true'); + const performLogout = async () => { + // Set logout flag to prevent TokenSync/PresenceProvider from restoring tokens + localStorage.setItem('isLoggingOut', 'true'); - // Clear all localStorage auth data - localStorage.removeItem('accessToken'); - localStorage.removeItem('refreshToken'); - localStorage.removeItem('user'); + // 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 + } + } - // Use signOut with callbackUrl - this ensures the server clears the - // session cookie BEFORE the redirect happens (server-side 302). - // Do NOT use redirect:false + manual redirect, as that causes a race - // condition where navigation happens before the cookie is cleared. - signOut({ callbackUrl: '/login' }); + // Clear all localStorage auth data + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + localStorage.removeItem('user'); + + // Manually clear next-auth cookies as fallback + // next-auth v5 uses these cookie names: + 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`; + }); + + // Use server action to properly clear session (redirects to /login) + try { + await logoutAction(); + } catch { + // Server action redirect throws in Next.js — this is expected. + // If it truly fails, the manual cookie clearing above ensures logout. + // Fallback redirect: + window.location.href = '/login'; + } + }; + + performLogout(); }, []); return (