Overview
The logout endpoint terminates the current user session by invalidating the access token and refresh token in Supabase Auth. This ensures the user is fully logged out and cannot access protected resources without re-authenticating.
Logout clears the session server-side. Make sure to also clear any client-side session data (cookies, localStorage) after calling this endpoint.
Endpoint
Authentication
Bearer token obtained from login. Format: Bearer {access_token}
Request
This endpoint does not require a request body. The user session is identified by the access token in the Authorization header.
Response
Always true when logout is successful.
Request Example
curl -X POST https://jcv24fitness.com/api/auth/logout \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
const response = await fetch ( 'https://jcv24fitness.com/api/auth/logout' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
}
});
const data = await response . json ();
const logout = async ( accessToken : string ) => {
const response = await fetch ( 'https://jcv24fitness.com/api/auth/logout' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
},
});
if ( ! response . ok ) {
throw new Error ( 'Logout failed' );
}
return response . json ();
};
Response Example
200 - Success
401 - Unauthorized
500 - Server Error
{
"success" : true ,
"message" : "Successfully logged out"
}
Error Codes
Code Description UNAUTHORIZEDAccess token is invalid, expired, or missing LOGOUT_ERRORServer-side error during logout process SUPABASE_ERRORInternal error from Supabase Auth service
Implementation Details
Logout Flow
Client sends POST request with Authorization header containing access token
Supabase Auth validates the token
If valid, Supabase invalidates both access token and refresh token
Server responds with success confirmation
Client clears local session data (cookies, localStorage, context state)
Client redirects user to login page or home page
Token Invalidation
When logout is called:
The current access token is immediately invalidated
The associated refresh token is revoked and cannot be used to get new access tokens
Any subsequent API requests with the old token will return 401 Unauthorized
The session is removed from Supabase’s session store
Client-Side Cleanup
After a successful logout, the client should:
Remove access token from storage
Remove refresh token from storage
Clear user context/state
Clear any cached user data
Redirect to public page
Frontend Integration
Using the Auth Hook
import { useAuth } from '@/features/auth' ;
import { useRouter } from 'next/navigation' ;
function LogoutButton () {
const { signOut } = useAuth ();
const router = useRouter ();
const handleLogout = async () => {
await signOut ();
// User is now logged out
// Redirect to home page
router . push ( '/' );
};
return (
< button onClick = { handleLogout } >
Cerrar sesión
</ button >
);
}
Complete Logout Component
import { useAuth } from '@/features/auth' ;
import { useRouter } from 'next/navigation' ;
import { useState } from 'react' ;
function LogoutButton () {
const { signOut , user } = useAuth ();
const router = useRouter ();
const [ isLoading , setIsLoading ] = useState ( false );
const [ error , setError ] = useState < string | null >( null );
const handleLogout = async () => {
if ( ! user ) return ;
setIsLoading ( true );
setError ( null );
try {
await signOut ();
// Optional: Clear additional local data
localStorage . removeItem ( 'wizard_data' );
// Redirect to home
router . push ( '/' );
} catch ( err ) {
setError ( 'Failed to logout. Please try again.' );
console . error ( 'Logout error:' , err );
} finally {
setIsLoading ( false );
}
};
return (
< div >
< button
onClick = { handleLogout }
disabled = { isLoading }
className = "btn-logout"
>
{ isLoading ? 'Cerrando sesión...' : 'Cerrar sesión' }
</ button >
{ error && (
< p className = "text-red-500 text-sm mt-2" > { error } </ p >
)}
</ div >
);
}
Auth Context Implementation
The signOut function in the Auth context:
const signOut = async () => {
const supabase = createClient ();
if ( ! supabase ) return ;
// Call Supabase signOut
await supabase . auth . signOut ();
// Context automatically updates state via onAuthStateChange listener
// No need to manually clear state here
};
The AuthProvider automatically listens to Supabase auth state changes, so when signOut() is called, the context state (user, session) is automatically cleared.
Session Cleanup
Automatic Cleanup
The AuthProvider handles automatic cleanup:
supabase . auth . onAuthStateChange (( event , session ) => {
if ( event === 'SIGNED_OUT' ) {
setState ({
user: null ,
session: null ,
profile: null ,
isLoading: false ,
isAuthenticated: false ,
});
}
});
Manual Cleanup (Optional)
For additional cleanup:
const handleLogout = async () => {
await signOut ();
// Clear wizard data
localStorage . removeItem ( 'wizard_data' );
// Clear payment data
localStorage . removeItem ( 'payment_info' );
// Clear any cached API responses
queryClient . clear ();
// Redirect
router . push ( '/login' );
};
Security Considerations
Always logout users when they close sensitive pages or after a period of inactivity.
Best Practices
Use secure token storage : Store tokens in httpOnly cookies when possible
Clear all session data : Remove tokens, user data, and cached content
Invalidate server-side : Always call the logout endpoint, don’t just clear client storage
Redirect immediately : Don’t leave users on protected pages after logout
Handle errors gracefully : Show user-friendly messages if logout fails
Automatic Logout Scenarios
Token expiration (1 hour for access tokens)
Session timeout after inactivity
User changes password (invalidates all sessions)
User initiates logout from another device (if implementing multi-device session management)
Multi-Tab Behavior
Supabase Auth automatically synchronizes session state across browser tabs:
If a user logs out in one tab, all other tabs are automatically updated
The onAuthStateChange listener fires in all tabs when logout occurs
No manual synchronization needed
// This happens automatically in all tabs
supabase . auth . onAuthStateChange (( event ) => {
if ( event === 'SIGNED_OUT' ) {
// Update UI in this tab
router . push ( '/login' );
}
});
Testing
Test Logout Success
import { render , screen , fireEvent , waitFor } from '@testing-library/react' ;
import { AuthProvider } from '@/features/auth' ;
import LogoutButton from './LogoutButton' ;
test ( 'should logout user successfully' , async () => {
render (
< AuthProvider >
< LogoutButton />
</ AuthProvider >
);
const button = screen . getByText ( 'Cerrar sesión' );
fireEvent . click ( button );
await waitFor (() => {
expect ( screen . queryByText ( 'Dashboard' )). not . toBeInTheDocument ();
});
});
Test Error Handling
test ( 'should handle logout error' , async () => {
// Mock supabase.auth.signOut to fail
const mockSignOut = jest . fn (). mockRejectedValue ( new Error ( 'Network error' ));
render (< LogoutButton />);
const button = screen . getByText ( 'Cerrar sesión' );
fireEvent . click ( button );
await waitFor (() => {
expect ( screen . getByText ( /Failed to logout/ i )). toBeInTheDocument ();
});
});