feat: Implement a server-side logout action and enhance client-side logout with backend token invalidation and comprehensive local storage/cookie clearing.
This commit is contained in:
7
src/app/logout/actions.ts
Normal file
7
src/app/logout/actions.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
'use server';
|
||||
|
||||
import { signOut } from '@/auth';
|
||||
|
||||
export async function logoutAction() {
|
||||
await signOut({ redirectTo: '/login' });
|
||||
}
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user