Appearance
Logout Flow
This page documents POST /api/v1/auth/logout.
This page was rewritten for v5
The previous version of this page described three separate flows — "Single Logout", "Global Logout" (POST /api/v1/auth/logout/all), and "Admin Force Logout" (POST /api/v1/admin/users/{userId}/logout) — with a request body containing refreshToken. None of that matches the current implementation. AuthController exposes exactly one logout endpoint, it takes no body, and — per LogoutCommandHandler's own log message — it is already a global logout: "Processing GLOBAL logout for user {UserId} - all sessions will be terminated". There is no selective/single-session logout, and no /logout/all or admin force-logout endpoint currently exists anywhere in the Auth module. If your product needs admin-initiated forced logout, treat it as a gap to raise with the Auth team, not an existing capability.
Overview
/api/v1/auth/logout is [AllowAnonymous] and idempotent: it always returns 204 No Content, whether or not a valid token was presented, and whether or not the underlying invalidation succeeds. There is no request/response body. The endpoint reads the user id and access-token jti directly from the claims of whatever Authorization header was sent.
Sequence Diagram
100% 💡 Use Ctrl + Scroll para zoom | Arraste para navegar
Step-by-Step Explanation
1. Send Logout Request
http
POST /api/v1/auth/logout
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...No body. If no Authorization header is present, or it doesn't carry a valid user id, the endpoint logs an idempotent no-op and still returns 204.
2. Invalidate the Session — Globally
Logout is global by design: it doesn't just kill the presented token, it invalidates every outstanding token for that user (all devices/sessions). This is implemented with the same two-layer mechanism used elsewhere in GrydAuth's defense-in-depth token validation:
- the presented access token's
jtiis blacklisted individually (immediate effect for that exact token); - the user's token-version / timestamp blacklist is bumped, so every other previously-issued token — on any device — fails validation on its next use.
csharp
public class User
{
public int TokenVersion { get; private set; }
public void InvalidateAllTokens(string reason)
{
TokenVersion++;
}
}On validation elsewhere in the system:
csharp
if (tokenVersion != user.TokenVersion)
{
// rejected — fail-secure
}If your product needs single-device logout
GrydAuth v5 does not support it today — logging out terminates every session for the user. Don't build client UX (e.g. "log out of this device only") that assumes otherwise.
3. Client Cleanup
typescript
async function logout(): Promise<void> {
try {
await authApi.logout(); // no body, no refreshToken param
} catch (error) {
// Logout is idempotent server-side (always 204) — this really only
// fires on network failure. Log and continue with client cleanup.
console.warn('Logout call failed (network):', error);
} finally {
authStore.clearAll();
router.push('/login');
}
}Error Scenarios
There isn't really an "error" path from the client's point of view — the endpoint is idempotent and always returns 204 No Content.
| Situation | HTTP Status | Client action |
|---|---|---|
| Valid token, invalidation succeeds | 204 | Clear local state, redirect to login |
| No/invalid token presented | 204 | Same — nothing to invalidate, treat as success |
| Underlying invalidation failure | 204 | Same — clear local state regardless; server logs a warning |
Because the endpoint never signals failure to the client, always clear local state unconditionally after calling it (or even if the call itself throws on the network layer).
Security Best Practices
1. Always Invalidate Server-Side
typescript
// ❌ Bad - Only client-side cleanup
function logout() {
localStorage.removeItem('token');
router.push('/login');
}
// ✅ Good - Server-side + client-side
async function logout() {
await authApi.logout();
authStore.clearAll();
router.push('/login');
}2. Clear All Sensitive Data
typescript
function clearAll(): void {
this.accessToken = null;
this.refreshToken = null;
this.user = null;
this.tenant = null;
this.permissions = [];
queryClient.clear();
}Never store tokens in localStorage
Refresh tokens especially should never be stored in localStorage due to XSS exposure. See Refresh Token — Security Considerations.
3. Treat Logout-Equivalent Errors the Same Way
Any authenticated request that comes back with one of these codes means the session is already gone server-side — force the same client cleanup as an explicit logout, without calling /auth/logout again first:
typescript
axios.interceptors.response.use(
response => response,
async error => {
const code = error.response?.data?.code;
if (error.response?.status === 401 &&
['TOKEN_REVOKED', 'SESSION_INVALIDATED'].includes(code)) {
authStore.clearAll();
router.push('/login');
return Promise.reject(error);
}
return Promise.reject(error);
}
);Related Flows
- Login Flow - Initial authentication
- Refresh Token - Token renewal (also subject to token-version invalidation)
- First Login - Password change also calls
InvalidateAllTokens