Logout - Aligheri/jwt-owasp-based-starter GitHub Wiki

Logout Implementation

Overview

This Spring Boot OWASP-based starter provides a secure logout mechanism that handles JWT token revocation, cookie deletion, and proper cleanup. The implementation follows OWASP security best practices for session management and token handling.

Key Features

  • JWT Token Revocation: Securely revokes JWT tokens by storing their digest in a blacklist
  • Cookie Management: Automatically deletes authentication cookies
  • Token Encryption: Uses encrypted token storage with SHA-256 hashing
  • Automatic Cleanup: Scheduled task to clean up revoked tokens every 30 days
  • Error Handling: Comprehensive exception handling with proper logging

Architecture Components

1. Authentication Service Interface

V logout(String jwtToken, HttpServletResponse response, String cookieName);

2. Token Revoker

Handles the core token revocation logic:

  • Calculates SHA-256 digest of decrypted tokens
  • Stores revoked token digests in database
  • Provides token revocation status checking
  • Automatic cleanup scheduling

3. Revoked Token Repository

JPA repository for managing revoked tokens in the database.

4. Database Entity

RevokedToken entity stores:

  • Unique ID
  • JWT token digest (SHA-256)
  • Revocation timestamp

Implementation Guide

Step 1: Database Setup

Ensure your database has the revoked tokens table:

CREATE TABLE revoked_token (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    jwt_token_digest VARCHAR(255) NOT NULL,
    revocation_date TIMESTAMP
);

Step 2: Controller Implementation

@PostMapping("/logout")
public ResponseEntity<DefaultRegisterResponse> logout(
    @RequestHeader("Authorization") String authHeader, 
    HttpServletResponse response) {
    
    String jwt = extractJwtFromHeader(authHeader);
    if (jwt == null) {
        return ResponseEntity.badRequest()
            .body(new DefaultRegisterResponse("Authorization header is missing or invalid"));
    }

    try {
        DefaultRegisterResponse result = authenticationService.logout(jwt, response, "fingerprint");
        return ResponseEntity.ok(result);
    } catch (IllegalArgumentException e) {
        logger.error("Invalid token format during logout: {}", e.getMessage());
        return ResponseEntity.badRequest()
            .body(new DefaultRegisterResponse("Invalid token format"));
    } catch (Exception e) {
        logger.error("Logout error: {}", e.getMessage(), e);
        return ResponseEntity.badRequest()
            .body(new DefaultRegisterResponse("Logout failed: " + e.getMessage()));
    }
}

private String extractJwtFromHeader(String authHeader) {
    if (authHeader == null || authHeader.isEmpty()) {
        return null;
    }
    return authHeader.startsWith("Bearer ") ? authHeader.substring(7) : authHeader;
}

Step 3: Service Configuration

Inject the required dependencies:

@Service
public class AuthenticationServiceImpl implements AuthenticationService {
    private final TokenRevoker tokenRevoker;
    private final CookieProvider cookieProvider;
    private final Supplier<V> registerResponseSupplier;
    
    // Implementation as shown in the provided code
}

Security Considerations

Token Digest Storage

  • Only SHA-256 digests are stored, never the actual tokens
  • Prevents token reconstruction even if database is compromised
  • Uses Base64 encoding for consistent storage format

Cookie Security

  • Automatic cookie deletion on logout
  • Configurable cookie names for flexibility
  • HttpOnly and Secure flags should be set on cookies

Error Handling

  • Generic error messages to prevent information disclosure
  • Detailed logging for debugging (server-side only)
  • Graceful degradation on system failures

Configuration Options

Cleanup Schedule

The system automatically schedules cleanup every 30 days at 3 AM. To customize:

// Modify scheduleRevokedTokensDeletion() method
long initialDelay = calculateInitialDelay(); // Your custom logic
long periodMinutes = 30 * 24 * 60; // 30 days in minutes

Cookie Configuration

Configure cookie names and properties in your application properties:

app.security.cookie.name=fingerprint
app.security.cookie.secure=true
app.security.cookie.http-only=true

API Usage

Request Format

POST /logout
Authorization: Bearer <your-jwt-token>

Response Format

{
    "message": "Logged out successfully!"
}

Error Responses

{
    "message": "Invalid token format"
}

Testing

Unit Tests

Test the core components:

  • TokenRevoker token revocation logic
  • Cookie deletion functionality
  • Error handling scenarios

Integration Tests

  • Full logout flow testing
  • Database interaction verification
  • Token revocation persistence

Monitoring and Logging

Key Log Events

  • Successful logouts: INFO level
  • Token processing errors: WARN level
  • System failures: ERROR level

Metrics to Monitor

  • Logout success/failure rates
  • Token revocation performance
  • Database cleanup execution

Troubleshooting

Common Issues

  1. "Invalid token format" errors

    • Check Authorization header format
    • Ensure Bearer prefix is included
    • Verify token is properly Base64 encoded
  2. Database connection issues

    • Verify RevokedTokenRepository configuration
    • Check database connectivity
    • Ensure table schema matches entity
  3. Cookie deletion failures

    • Verify cookie name configuration
    • Check domain/path settings
    • Ensure response object is properly injected

Debug Mode

Enable debug logging for detailed troubleshooting:

logging.level.your.package.name=DEBUG

Migration Notes

When upgrading or migrating:

  1. Backup existing revoked tokens table
  2. Update database schema if needed
  3. Test token revocation functionality
  4. Verify cookie handling works correctly
  5. Monitor error logs during transition period