Skip to content

Logout Flow

This page documents the logout flow in GrydAuth, which properly invalidates tokens and cleans up session data.

Overview

Logout in GrydAuth is more than just clearing client-side tokens. It involves server-side token invalidation to prevent unauthorized use of stolen tokens and maintains security audit trails.

Logout Types

TypeDescriptionUse Case
Single LogoutInvalidates current session onlyUser clicks logout button
Global LogoutInvalidates all sessionsPassword change, security concern
Admin LogoutForce logout specific userSecurity incident response

Single Logout Sequence Diagram

100% 💡 Use Ctrl + Scroll para zoom | Arraste para navegar

Global Logout (All Sessions)

When a user wants to log out from all devices or after a password change:

100% 💡 Use Ctrl + Scroll para zoom | Arraste para navegar

Admin Force Logout

Administrators can force logout specific users:

100% 💡 Use Ctrl + Scroll para zoom | Arraste para navegar

Step-by-Step Explanation

Single Logout

1. Send Logout Request

http
POST /api/auth/logout
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json

{
  "refreshToken": "550e8400-e29b-41d4-a716-446655440000"
}

2. Invalidate Refresh Token

The refresh token is deleted from Redis, preventing future token refresh.

3. Blacklist Access Token (Optional)

For immediate invalidation before natural expiration:

csharp
var tokenId = jwt.Claims.First(c => c.Type == "jti").Value;
var expiration = jwt.ValidTo - DateTime.UtcNow;

await _cache.SetAsync(
    $"blacklist:{tokenId}", 
    true, 
    expiration
);

4. Audit Logging

All logout events are recorded for security auditing.

5. Client Cleanup

typescript
async function logout(): Promise<void> {
  const refreshToken = authStore.getRefreshToken();
  
  try {
    await authApi.logout({ refreshToken });
  } catch (error) {
    // Log error but continue with client cleanup
    console.error('Server logout failed:', error);
  } finally {
    // Always clear client state
    authStore.clearTokens();
    authStore.clearUser();
    router.push('/login');
  }
}

Global Logout

Token Version Mechanism

Instead of tracking all active tokens, GrydAuth uses a token version:

csharp
public class User
{
    public int TokenVersion { get; private set; }
    
    public void InvalidateAllTokens()
    {
        TokenVersion++;
    }
}

JWT tokens include the version:

json
{
  "sub": "user-id",
  "token_version": 4,
  ...
}

On validation:

csharp
if (tokenVersion != user.TokenVersion)
{
    throw new SecurityTokenException("Token invalidated");
}

Admin Force Logout

Used for:

  • Security incident response
  • Employee termination
  • Suspicious activity detection
  • Compliance requirements

Token Blacklist Strategy

100% 💡 Use Ctrl + Scroll para zoom | Arraste para navegar
csharp
public class LogoutOptions
{
    // Always invalidate refresh token
    public bool InvalidateRefreshToken { get; set; } = true;
    
    // Blacklist access token for immediate invalidation
    public bool BlacklistAccessToken { get; set; } = true;
    
    // For short-lived tokens (< 5 min), skip blacklist
    public TimeSpan BlacklistThreshold { get; set; } = TimeSpan.FromMinutes(5);
}

Error Scenarios

ErrorHTTP StatusCauseAction
Invalid token401Already logged out or expiredProceed to login
Missing refresh token400Client didn't send refresh tokenBest effort logout
Server error500Cache or DB failureClear client state anyway

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({ refreshToken });
  authStore.clearAll();
  router.push('/login');
}

2. Handle Logout Failures Gracefully

typescript
async function logout(): Promise<void> {
  try {
    await authApi.logout({ refreshToken });
  } catch (error) {
    // Server logout failed - token might already be invalid
    console.warn('Server logout failed:', error);
  }
  
  // Always clear client state
  authStore.clearAll();
}

3. Clear All Sensitive Data

typescript
function clearAll(): void {
  // Clear tokens
  this.accessToken = null;
  this.refreshToken = null;
  
  // Clear user data
  this.user = null;
  this.tenant = null;
  this.permissions = [];
  
  // Clear any cached data
  queryClient.clear();
  
  // Clear cookies if used
  document.cookie = 'session=; expires=Thu, 01 Jan 1970';
}

4. Implement Logout on Token Expiry

typescript
// When refresh token fails, trigger logout
axios.interceptors.response.use(
  response => response,
  async error => {
    if (error.response?.status === 401) {
      const errorCode = error.response.data?.error;
      
      if (['token_revoked', 'session_terminated'].includes(errorCode)) {
        // Force logout - don't try to refresh
        await logout();
        return Promise.reject(error);
      }
    }
    return Promise.reject(error);
  }
);

Configuration

json
{
  "LogoutSettings": {
    "BlacklistAccessTokens": true,
    "BlacklistThresholdMinutes": 5,
    "SendLogoutNotification": true,
    "EnableGlobalLogout": true,
    "EnableAdminForceLogout": true
  }
}

Audit Log Example

json
{
  "eventType": "UserLogout",
  "timestamp": "2026-02-02T14:30:00Z",
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "tenantId": "tenant-123",
  "details": {
    "logoutType": "single",
    "ipAddress": "192.168.1.100",
    "userAgent": "Mozilla/5.0...",
    "refreshTokenInvalidated": true,
    "accessTokenBlacklisted": true
  }
}

Released under the MIT License.