Logout and Token Expiry
Logout clears the token and user state. Token expiry needs to be handled automatically — when the API returns 401, the user should be logged out and redirected gracefully.
The logout function
Section titled “The logout function”The logout function in AuthContext handles everything:
function logout() { localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(USER_KEY) setToken(null) setUser(null)}Calling logout() from any component triggers a re-render of the entire tree — isAuthenticated becomes false, the navbar updates, and protected routes redirect.
Logout button
Section titled “Logout button”// In Navbar.tsxconst { user, logout } = useAuth()
function handleLogout() { logout() navigate('/')}
<button onClick={handleLogout}>Logout</button>Handling 401 — token expiry
Section titled “Handling 401 — token expiry”When the JWT expires (after 24 hours), the API returns 401 Unauthorized. The axios response interceptor can handle this globally:
let logoutCallback: (() => void) | null = null
export function setLogoutCallback(fn: () => void) { logoutCallback = fn}
apiClient.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401 && logoutCallback) { logoutCallback() } return Promise.reject(error) })Register the callback in AuthProvider:
import { setLogoutCallback } from '../api/client'
export function AuthProvider({ children }: { children: ReactNode }) { // ...
function logout() { localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(USER_KEY) setToken(null) setUser(null) }
// Register logout with the axios interceptor useEffect(() => { setLogoutCallback(logout) return () => setLogoutCallback(null as any) }, [])
// ...}Now any 401 response automatically calls logout() — the user is logged out and the navbar updates without any per-component handling.
Showing a “session expired” message
Section titled “Showing a “session expired” message”Optionally, show a message when auto-logged out:
function logout(reason?: 'expired') { localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(USER_KEY) setToken(null) setUser(null) if (reason === 'expired') { // Navigate to login with a message window.location.href = '/login?expired=true' }}// In LoginPage.tsxconst expired = new URLSearchParams(window.location.search).get('expired'){expired && <p className="notice">Your session expired. Please log in again.</p>}Token refresh (advanced — not in Bulletin)
Section titled “Token refresh (advanced — not in Bulletin)”Production apps often use refresh tokens:
- Short-lived access token (15 minutes)
- Long-lived refresh token (7 days)
- When the access token expires, automatically exchange the refresh token for a new one
This is more complex but eliminates the “session expired” UX problem. Bulletin uses a simple 24-hour token — acceptable for a learning app.
Exercise
Section titled “Exercise”- Add a Logout button to the Navbar that calls
handleLogout. - Set up the 401 interceptor with the callback pattern.
- Test: register, wait for the token to expire (change
expiresIn: '5s'in the API temporarily), make a request, confirm logout happens.
logout()clears localStorage and React state — triggers re-render of the entire tree.- The axios 401 interceptor handles token expiry globally — no per-component handling needed.
- Register the logout callback from
AuthProviderusing auseEffect. - Token refresh is the production solution to session expiry — acceptable to skip for learning.