13.13. Logout
Table of Contents
Why Logout Matters
Logout looks very simple to the user, but it is a critical security feature.
A user clicks “Logout,” and you must make sure that:
- Their current session or token can no longer be used.
- Any sensitive data is cleared from the browser and the server.
- They cannot accidentally stay logged in on a shared computer.
If logout is weak or incomplete, attackers can reuse old cookies or tokens and access accounts.
Rule: Logout is not “just hide the UI.”
You must invalidate authentication credentials (session, token, etc.) on the server side.
In this chapter you will see how logout works with sessions, tokens, and common backend patterns.
Logout in Session-Based Authentication
In session-based systems, the browser stores a session cookie like:
Cookie: session_id=abc123
The server stores session data for abc123 in memory, Redis, or a database.
To log out, you must do two things:
- Remove or invalidate the server-side session.
- Remove the cookie in the browser.
Server-Side Session Invalidation
For a typical web framework, logout might:
def logout(request):
session_id = request.cookies.get("session_id")
if session_id:
delete_session_from_store(session_id) # remove from DB / Redis
response = redirect_to_login()
response.delete_cookie("session_id") # tell browser to drop cookie
return response
The key part is delete_session_from_store. After this, even if the cookie is stolen or not deleted yet, the server will not find a valid session.
Common mistakes:
- Only deleting the cookie and leaving the session in the database.
- Only clearing session data in memory and not removing the record.
If your session store is Redis, logout might be as simple as:
redis_client.delete(f"session:{session_id}")or in SQL:
DELETE FROM sessions WHERE id = 'abc123';Rule: With sessions, logout must destroy or invalidate the server-side session record, not just rely on the browser.
Clearing the Session Cookie
The backend tells the browser to remove the cookie by setting it again with an expiration in the past:
Set-Cookie: session_id=deleted; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SecureFrameworks usually provide helpers like:
response.delete_cookie("session_id")Important details:
- Use the same
Path,Domain, andSecure/HttpOnlysettings as when you set the cookie. - If they differ, the browser might keep an old cookie.
Logout Flow Example (Sessions)
- User clicks “Logout” link that sends
POST /logoutwith cookiesession_id=abc123. - Backend handler:
- Reads session id from cookie.
- Deletes session from store.
- Returns response that clears cookie and redirects to
/login. - Any future request with
session_id=abc123will fail, since the session is gone.
Logout in Token-Based Authentication
In token-based systems (for example JWTs) the browser or client stores an access token, sometimes a refresh token too.
A typical Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Basic idea
- The token itself is the proof of authentication.
- The server often does not store each access token in a database.
- Token expiry is embedded in the token, for example
$exp$claim in JWT.
So how do you “logout” if the server does not store tokens?
You have two jobs:
- On the client, remove the token.
- On the server, optionally prevent reuse of some tokens (especially refresh tokens).
Client-Side Token Removal
For a single-page application:
function logout() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
window.location.href = '/login';
}
Or, if you used HttpOnly cookies for tokens, the backend must send Set-Cookie headers that clear those cookies, just like with session cookies.
Example response on logout:
Set-Cookie: access_token=; HttpOnly; Secure; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
Set-Cookie: refresh_token=; HttpOnly; Secure; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMTServer-Side Token Revocation
If tokens are short-lived (for example 5 minutes) and only access tokens exist, many systems do not store them. Logout is basically:
- Client forgets the token.
- Server just waits for the token to expire.
However, when you use refresh tokens, you typically store them in a database. Logout must revoke or delete them.
Example data:
| user_id | refresh_token_id | token_value | expires_at |
|---|---|---|---|
| 5 | 1 | 9f21... | 2026-12-01 12:00:00 |
On logout:
def logout(current_user, refresh_token_id):
db.execute(
"DELETE FROM refresh_tokens WHERE id = %s AND user_id = %s",
(refresh_token_id, current_user.id)
)
response = Response(status_code=204)
response.delete_cookie("refresh_token")
response.delete_cookie("access_token")
return responseNow that refresh token can never be used to get a new access token.
Rule: In token-based systems with refresh tokens, logout must revoke or delete the refresh token on the server.
Logout Endpoints and HTTP Methods
A logout is not a simple “view” of data, it changes authentication state. Because of that, you should not use GET /logout with no protection.
A safer design:
- Use
POST /logout(orDELETE /sessions/current). - Require a valid access token or session.
- Remove server-side credentials.
- Clear cookies.
- Return 204 No Content or 200 OK.
Example REST design:
| Action | Method | Path | Description |
|---|---|---|---|
| Login | POST | /auth/login | Create session / token |
| Logout | POST | /auth/logout | Destroy current session |
| Logout all | POST | /auth/logout-all | Invalidate all sessions |
Example FastAPI style logout handler (session-based):
from fastapi import APIRouter, Response, Depends
router = APIRouter()
@router.post("/logout")
def logout(response: Response, current_user=Depends(get_current_user)):
# remove user sessions from store, example:
delete_sessions_for_user(current_user.id)
response.delete_cookie("session_id")
return {"detail": "Logged out"}You typically want CSRF protection for logout if it is cookie-based, since a POST request can be triggered from another site.
Logout from All Devices
Sometimes the user clicks “Logout from all devices” or “Log out everywhere.” This is especially important for security after password change.
The implementations differ for sessions vs tokens.
Sessions: Invalidate All User Sessions
Your session store may link sessions to users:
| session_id | user_id | created_at |
|---|---|---|
| abc123 | 42 | 2026-01-01 10:00:00 |
| def456 | 42 | 2026-01-02 13:00:00 |
To logout from all devices:
DELETE FROM sessions WHERE user_id = 42;Every device that tries to use an old cookie will fail.
Tokens: Invalidate All Refresh Tokens
If you store refresh tokens:
| id | user_id | token_value | expires_at |
|---|---|---|---|
| 1 | 42 | aaaa... | 2026-02-01 00:00:00 |
| 2 | 42 | bbbb... | 2026-02-15 00:00:00 |
Logout all devices:
DELETE FROM refresh_tokens WHERE user_id = 42;Optionally, for access tokens, many systems rely on short lifetimes and do not store them. After logout-all:
- All refresh tokens are invalid.
- Remaining access tokens die naturally after a short time.
Handling Logout in Single-Page Applications (SPAs)
In SPAs, the frontend often controls navigation and state. Backend still controls security.
A typical SPA logout flow:
- User clicks “Logout” button.
- Frontend sends
POST /auth/logoutto backend with the current credentials. - Backend:
- Deletes session or revokes refresh token.
- Clears cookies if used.
- Returns 204.
- Frontend:
- Clears any local storage tokens.
- Clears any user state in memory (like React context or Vuex).
- Redirects to
/loginor a public page.
Example (React-ish pseudocode):
async function handleLogout() {
try {
await api.post('/auth/logout'); // backend invalidates
} catch (e) {
// ignore errors, still clear local state
}
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
setUser(null);
navigate('/login');
}Rule: Frontend must clear its own copy of authentication data, but real logout security depends on what the backend does.
Security Considerations and Common Pitfalls
Logout is part of your security design. Done poorly, it gives a false feeling of safety.
Using GET /logout Without Protection
If logout is triggered by a simple GET /logout, any site can embed an image:
<img src="https://your-app.com/logout" />This would log users out without their intention. While this is not as bad as logging them in or changing data, it can be annoying and may break flows.
A better design:
- Use
POST /logout. - Use CSRF protection for cookie-based authentication.
Not Invalidating Server-Side State
Common mistake:
- Only deleting the cookie.
- Or only clearing localStorage.
If sessions or refresh tokens remain valid, an attacker who already has a copy can keep using them.
Always:
- Destroy sessions.
- Revoke refresh tokens.
Not Handling Multiple Devices
Users may log in from:
- Laptop
- Phone
- Shared computer
Profile actions like “Change password” or “Report stolen phone” should often:
- Trigger “logout all devices”.
- Maybe create a different “logout all devices except this one”.
This means your data model must support finding all active sessions or tokens for a user.
Long-Lived Tokens Without Revocation
If access tokens live for many days and you do not have any revocation list, logout becomes weak. An attacker who steals the token can keep using it until it expires.
Safer approach:
- Use short-lived access tokens, for example 5 to 15 minutes.
- Use longer-lived refresh tokens with server-side storage and revocation on logout.
Implementing Token Blacklists
If you must be able to immediately revoke access tokens before they expire, you can keep a blacklist or a “version” strategy.
Blacklist example
- On logout, store the token id (for example JWT
jticlaim) in a blacklist store with its expiry. - On every request, check if
jtiis blacklisted.
This adds overhead, but allows instant logout.
Version (token version) example
- Add a
token_versionto the user record. - Include
token_versionin the JWT when issuing it. - On logout-all, increment
token_versionin the database. - When validating a token, check that the token’s
token_versionmatches the user’s current one.
Any old tokens with the previous version are automatically invalid.
Example table:
| user_id | token_version |
|---|---|
| 100 | 5 |
On logout-all:
UPDATE users SET token_version = token_version + 1 WHERE id = 100;Token payload:
{
"sub": 100,
"token_version": 5,
"exp": 1735707600
}
If token_version in DB becomes 6, all old tokens with token_version: 5 are invalid.
User Experience Around Logout
While security is the main goal, UX also matters.
Good practices:
- Clear feedback: Show a “You have been logged out” message or redirect to a known page.
- Idempotent behavior: If a user hits logout twice, do not show scary errors; just treat second logout as success again.
- Safe redirects: After logout, redirect only to safe URLs:
- Do not trust arbitrary
next=query parameters from external sites. - Instead, validate redirect targets or use a fixed one like
/login.
Example response:
{
"detail": "Logged out successfully"
}Even if the session did not exist, returning success is usually fine. Do not leak information about whether the user was logged in or not.
Summary
You have seen how logout works for different authentication styles:
- Sessions: Delete the session in the store and clear the cookie.
- Tokens: Remove tokens from client storage and revoke refresh tokens (and maybe access tokens).
- Logout endpoints: Use
POST /logout, not unprotectedGET /logout. - Logout all devices: Delete all sessions or tokens for a user.
- Security: Proper logout must invalidate credentials on the server side, not just hide the UI or clear local state.
With these patterns, you can implement logout that is both user friendly and secure.
Views: 6
KAHIBARO