# Refresh Token Implementation ## Overview This document describes the refresh token functionality implemented for StoryCove, allowing users to stay authenticated for up to 2 weeks with automatic token refresh. ## Architecture ### Token Types 1. **Access Token (JWT)** - Lifetime: 24 hours - Stored in: httpOnly cookie + localStorage - Used for: API authentication - Format: JWT with subject and libraryId claims 2. **Refresh Token** - Lifetime: 14 days (2 weeks) - Stored in: httpOnly cookie + database - Used for: Generating new access tokens - Format: Secure random 256-bit token (Base64 encoded) ### Token Flow 1. **Login** - User provides password - Backend validates password - Backend generates both access token and refresh token - Both tokens sent as httpOnly cookies - Access token also returned in response body for localStorage 2. **API Request** - Frontend sends access token via Authorization header and cookie - Backend validates access token - If valid: Request proceeds - If expired: Frontend attempts token refresh 3. **Token Refresh** - Frontend detects 401/403 response - Frontend automatically calls `/api/auth/refresh` - Backend validates refresh token from cookie - If valid: New access token generated and returned - If invalid/expired: User redirected to login 4. **Logout** - Frontend calls `/api/auth/logout` - Backend revokes refresh token in database - Both cookies cleared - User redirected to login page ## Backend Implementation ### New Files 1. **`RefreshToken.java`** - Entity class - Fields: id, token, expiresAt, createdAt, revokedAt, libraryId, userAgent, ipAddress - Helper methods: isExpired(), isRevoked(), isValid() 2. **`RefreshTokenRepository.java`** - Repository interface - findByToken(String) - deleteExpiredTokens(LocalDateTime) - revokeAllByLibraryId(String, LocalDateTime) - revokeAll(LocalDateTime) 3. **`RefreshTokenService.java`** - Service class - createRefreshToken(libraryId, userAgent, ipAddress) - verifyRefreshToken(token) - revokeToken(token) - revokeAllByLibraryId(libraryId) - cleanupExpiredTokens() - Scheduled daily at 3 AM ### Modified Files 1. **`JwtUtil.java`** - Added `refreshExpiration` property (14 days) - Added `generateRefreshToken()` method - Added `getRefreshExpirationMs()` method 2. **`AuthController.java`** - Updated `/login` endpoint to create and return refresh token - Added `/refresh` endpoint to handle token refresh - Updated `/logout` endpoint to revoke refresh token - Added helper methods: `getRefreshTokenFromCookies()`, `getClientIpAddress()` 3. **`SecurityConfig.java`** - Added `/api/auth/refresh` to public endpoints 4. **`application.yml`** - Added `storycove.jwt.refresh-expiration: 1209600000` (14 days) ## Frontend Implementation ### Modified Files 1. **`api.ts`** - Added automatic token refresh logic in response interceptor - Added request queuing during token refresh - Prevents multiple simultaneous refresh attempts - Automatically retries failed requests after refresh ### Token Refresh Logic ```typescript // On 401/403 response: 1. Check if already retrying -> if yes, queue request 2. Check if refresh/login endpoint -> if yes, logout 3. Attempt token refresh via /api/auth/refresh 4. If successful: - Update localStorage with new token - Retry original request - Process queued requests 5. If failed: - Clear token - Redirect to login - Reject queued requests ``` ## Security Features 1. **httpOnly Cookies**: Prevents XSS attacks 2. **Token Revocation**: Refresh tokens can be revoked 3. **Database Storage**: Refresh tokens stored server-side 4. **Expiration Tracking**: Tokens have strict expiration dates 5. **IP & User Agent Tracking**: Stored for security auditing 6. **Library Isolation**: Tokens scoped to specific library ## Database Schema ```sql CREATE TABLE refresh_tokens ( id UUID PRIMARY KEY, token VARCHAR(255) UNIQUE NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL, revoked_at TIMESTAMP, library_id VARCHAR(255), user_agent VARCHAR(255) NOT NULL, ip_address VARCHAR(255) NOT NULL ); CREATE INDEX idx_refresh_token ON refresh_tokens(token); CREATE INDEX idx_expires_at ON refresh_tokens(expires_at); ``` ## Configuration ### Backend (`application.yml`) ```yaml storycove: jwt: expiration: 86400000 # 24 hours (access token) refresh-expiration: 1209600000 # 14 days (refresh token) ``` ### Environment Variables No new environment variables required. Existing `JWT_SECRET` is used. ## Testing Comprehensive test suite in `RefreshTokenServiceTest.java`: - Token creation - Token validation - Expired token handling - Revoked token handling - Token revocation - Cleanup operations Run tests: ```bash cd backend mvn test -Dtest=RefreshTokenServiceTest ``` ## Maintenance ### Automated Cleanup Expired tokens are automatically cleaned up daily at 3 AM via scheduled task in `RefreshTokenService.cleanupExpiredTokens()`. ### Manual Revocation ```java // Revoke all tokens for a library refreshTokenService.revokeAllByLibraryId("library-id"); // Revoke all tokens (logout all users) refreshTokenService.revokeAll(); ``` ## User Experience 1. **Seamless Authentication**: Users stay logged in for 2 weeks 2. **Automatic Refresh**: Token refresh happens transparently 3. **No Interruptions**: API calls succeed even when access token expires 4. **Backend Restart**: Users must re-login (JWT secret rotates on startup) 5. **Cross-Device Library Switching**: Automatic library switching when using different devices with different libraries ## Cross-Device Library Switching ### Feature Overview The system automatically detects and switches libraries when you use different devices authenticated to different libraries. This ensures you always see the correct library's data. ### How It Works **Scenario 1: Active Access Token (within 24 hours)** 1. Request comes in with valid JWT access token 2. `JwtAuthenticationFilter` extracts `libraryId` from token 3. Compares with `currentLibraryId` in backend 4. **If different**: Automatically switches to token's library 5. **If same**: Early return (no overhead, just string comparison) 6. Request proceeds with correct library **Scenario 2: Token Refresh (after 24 hours)** 1. Access token expired, refresh token still valid 2. `/api/auth/refresh` endpoint validates refresh token 3. Extracts `libraryId` from refresh token 4. Compares with `currentLibraryId` in backend 5. **If different**: Automatically switches to token's library 6. **If same**: Early return (no overhead) 7. Generates new access token with correct `libraryId` **Scenario 3: After Backend Restart** 1. `currentLibraryId` is null (no active library) 2. First request with any token automatically switches to that token's library 3. Subsequent requests use early return optimization ### Performance **When libraries match** (most common case): - Simple string comparison: `libraryId.equals(currentLibraryId)` - Immediate return - zero overhead - No datasource changes, no reindexing **When libraries differ** (switching devices): - Synchronized library switch - Datasource routing updated instantly - Solr reindex runs asynchronously (doesn't block request) - Takes 2-3 seconds in background ### Edge Cases **Multi-device simultaneous use:** - If two devices with different libraries are used simultaneously - Last request "wins" and switches backend to its library - Not recommended but handled gracefully - Each device corrects itself on next request **Library doesn't exist:** - If token contains invalid `libraryId` - Library switch fails with error - Request is rejected with 500 error - User must re-login with valid credentials ## Future Enhancements Potential improvements: 1. Persistent JWT secret (survive backend restarts) 2. Sliding refresh token expiration (extend on use) 3. Multiple device management (view/revoke sessions) 4. Configurable token lifetimes via environment variables 5. Token rotation (new refresh token on each use) 6. Thread-local library context for true stateless operation ## Summary The refresh token implementation provides a robust, secure authentication system that balances user convenience (2-week sessions) with security (short-lived access tokens, automatic refresh). The implementation follows industry best practices and provides a solid foundation for future enhancements.