Clearing a session cookie on the client has no effect on a session token that was already captured — by an XSS payload, a network sniff, or a shared machine where the token was read from memory. CWE-613 (Insufficient Session Expiration) applies when logout does not invalidate the server-side session record. NIST 800-63B §7.1 (Session Bindings) requires that session termination be enforced server-side, not only client-side. OWASP A07 (Identification and Authentication Failures) lists insufficient logout among its named failure modes. The server must forget the session; the cookie clear is only a UX courtesy.
High because client-only logout leaves a captured session token valid until its natural expiry, giving attackers continued access even after the legitimate user has explicitly logged out.
Delete the session record on the server in the same response that clears the cookie:
// src/app/api/auth/logout/route.ts
export async function POST(request: Request) {
const sessionToken = getSessionTokenFromRequest(request)
if (sessionToken) {
await db.session.delete({ where: { token: sessionToken } })
}
const response = Response.json({ ok: true })
response.headers.set('Set-Cookie',
'session=; Max-Age=0; Path=/; HttpOnly; Secure; SameSite=Lax'
)
return response
}
For stateless JWT access tokens, add the token to a short-lived Redis blocklist on logout, or keep access token lifetime at 5–15 minutes so the window of post-logout validity is minimal.
ID: saas-authentication.auth-flow.logout-invalidates-session
Severity: high
What to look for: Examine the logout handler (typically POST /api/auth/signout or similar). Verify it not only clears the client-side cookie but also invalidates the session server-side. For database sessions: check that the session record is deleted from the sessions table. For JWT-only (stateless) sessions: there is no server-side invalidation mechanism — check if a token blocklist/denylist is maintained. Managed auth providers handle this correctly. Count all instances found and enumerate each.
Pass criteria: Logout deletes or invalidates the server-side session record. Client-side cookie is also cleared. For JWT-only systems, either short token lifetime (< 15 minutes) compensates for the inability to revoke, or a blocklist is maintained. At least 1 implementation must be confirmed.
Fail criteria: Logout only clears the client-side cookie without invalidating the server-side session — the session token remains valid and could be reused if the cookie value was captured.
Skip (N/A) when: Fully managed auth provider (NextAuth, Clerk, Supabase Auth) used with default signout — they handle server-side invalidation. Signal: logout calls the provider's signout method directly.
Detail on fail: "Logout handler at /api/auth/logout clears the session cookie but does not delete the session record from the database — stolen tokens remain valid until expiry".
Remediation: Clearing a cookie doesn't prevent someone who already captured the cookie value from using it. The server must forget the session:
// Database session approach
export async function POST(request: Request) {
const sessionToken = getSessionTokenFromRequest(request)
if (sessionToken) {
await db.session.delete({ where: { token: sessionToken } })
}
const response = Response.json({ ok: true })
response.headers.set('Set-Cookie',
'session=; Max-Age=0; Path=/; HttpOnly; Secure; SameSite=Lax'
)
return response
}
For JWT-based systems, add tokens to a short-lived blocklist (Redis) on logout if immediate invalidation is needed. Otherwise, keep access token lifetime very short (5-15 minutes).