Appearance
🔐 Second Token Flow for ERP System
📋 Overview
Modern dual-token authentication architecture where users obtain a specialized ERP token after initial Keycloak authentication, enabling secure session management with Redis-backed token storage.
🔄 Authentication Flow
Phase 1: Initial Authentication
🔑 User authenticates via Keycloak SSO
📨 Receives primary JWT token
➡️ Redirected to after-login page
Phase 2: Second Token Acquisition
📡 After-login page requests ERP-specific token
✅ ERP backend validates Keycloak JWT
🎫 Generates second token with unique version identifier
💾 Stores token AND version in Redis
Phase 3: Distributed Token Storage
💻 Frontend Layer
Receives ERP token and token version from backend
Stores both in secure client storage (sessionStorage recommended)
Attaches token to all subsequent ERP API requests
Sends token version in request headers for validation
🗄️ Backend Layer
Persists token with version in Redis
Maps token to user session context
Validates token version on each request
Issues refresh directive if version mismatch detected
Configures automatic TTL-based expiration
Sequence Diagram
::: mermaid
sequenceDiagram
actor User
participant KC as Keycloak
participant FE as Frontend
(after-login)
participant BE as ERP Backend
participant Redis as Redis
User->>KC: 1. Authenticate (username/password)
activate KC
KC->>KC: Validate credentials
KC-->>User: 2. First Token (JWT)
deactivate KC
User->>FE: 3. Redirect with First Token
activate FE
FE->>BE: 4. POST /api/erp/token
(Authorization: Bearer firstToken)
activate BE
BE->>BE: 5. Validate First Token
(verify with Keycloak)
BE->>BE: 6. Generate Second Token
+ Token Version (UUID)
BE->>Redis: 7. SETEX erp:token:userId
(token, version, TTL: 3600s)
activate Redis
Redis-->>BE: 8. OK
deactivate Redis
BE-->>FE: 9. { secondToken, tokenVersion }
X-Token-Version header
deactivate BE
FE->>FE: 10. Store Token + Version
(sessionStorage)
FE-->>User: 11. Display Application
deactivate FE
Note over FE,BE: Subsequent API Requests
FE->>BE: 12. API Request
Authorization: Bearer token
X-Token-Version:
activate BE
BE->>Redis: 13. Get Token Version
activate Redis
Redis-->>BE: 14. Current Version
deactivate Redis
alt Version Match
BE-->>FE: 15. 200 OK + Data
else Version Mismatch
BE-->>FE: 16. 200 OK + Data
X-Token-Refresh-Required: true
FE->>FE: 17. Request New Token
end
deactivate BE
:::
Component Diagram
::: mermaid
graph TB
subgraph Client["Client Side"]
Browser[Browser]
AfterLogin[After-Login Page]
SessionStore[(Session Storage
Token + Version)]
end
subgraph Auth["Authentication"]
KC[Keycloak]
end
subgraph ERP["ERP Application"]
subgraph Backend["Backend"]
Gateway[API Gateway]
AuthMW[Auth Middleware
Version Validator]
TokenService[Token Service]
end
subgraph Storage["Storage"]
Redis[(Redis
Token + Version
TTL: 3600s)]
end
end
Browser -->|1. Authenticate| KC
KC -->|2. First Token| Browser
Browser -->|3. Navigate with token| AfterLogin
AfterLogin -->|4. Request Second Token| Gateway
Gateway -->|5. Validate| AuthMW
AuthMW -->|6. Issue Token + Version| TokenService
TokenService -->|7. Store Token + Version| Redis
Redis -->|8. Confirm| TokenService
TokenService -->|9. Return Token + Version| AfterLogin
AfterLogin -->|10. Store Both| SessionStore
SessionStore -.->|11. API Requests| Gateway
Gateway -.->|12. Validate Version| Redis
Gateway -.->|13. Refresh if mismatch| SessionStore
style Redis fill:#e1f5ff
style SessionStore fill:#e1f5ff
style KC fill:#fff4e1
style AuthMW fill:#ffe1e1
:::
🛡️ Security Architecture
| Component | Security Measure | Implementation |
|-----------|-----------------|----------------|
| Token Validation | Keycloak JWT verification | Cryptographic signature validation |
| Version Control | Token version matching | UUID-based version tracking in Redis |
| Storage Security | Client-side protection | SessionStorage (cleared on tab close) |
| Token Lifecycle | Automatic expiration | Synchronized TTL across layers |
| Cache Layer | Redis TTL enforcement | Auto-cleanup of expired sessions |
| Transport Security | TLS encryption | HTTPS-only communication |
| Token Rotation | Proactive refresh | Version mismatch detection |
| Stale Token Prevention | Version validation | Force refresh on version mismatch |
📡 HTTP Headers Specification
Request Headers (Frontend → Backend)
| Header Name | Type | Description | Example Value | When Used |
|-------------|------|-------------|---------------|-----------|
| Authorization | Standard | Bearer token for authentication | Bearer eyJhbGc... | All authenticated requests |
| X-Token-Version | Custom | Token version identifier | 550e8400-e29b-41d4-a716-446655440000 | All API requests after token acquisition |
Response Headers (Backend → Frontend)
| Header Name | Type | Description | Example Value | When Used |
|-------------|------|-------------|---------------|-----------|
| X-Token-Version | Custom | Token version on issuance | 550e8400-e29b-41d4-a716-446655440000 | Initial token response |
| X-Token-Refresh-Required | Custom | Signals version mismatch | true | When frontend version ≠ Redis version |
| X-Token-Expires-In | Custom | TTL in seconds | 3600 | Token issuance/refresh |
Header Naming Convention
Standard: Following RFC 7235 for
AuthorizationCustom Headers: Using
X-prefix for proprietary headers (legacy convention, widely supported)Alternative Modern Approach: Could use vendor-specific prefix like
ERP-Token-Versionfor clarity
⚙️ Technical Stack
Frontend Technologies
HTTP Client: Fetch API / Axios with interceptors
Storage: Web Storage API (SessionStorage)
Auth Flow: OAuth 2.0 / OpenID Connect
Version Tracking: UUID v4 generation and validation
Backend Technologies
Auth Provider: Keycloak
Cache Layer: Redis with versioned key storage
Token Format: JWT (JSON Web Tokens)
API Protocol: REST / GraphQL
Version Generation: UUID v4 (RFC 4122)
Infrastructure
Session Store: Redis Cluster
TTL Management: Automated expiration
Load Balancing: Session affinity support
Version Storage: Hash structure in Redis
Token Lifecycle
::: mermaid
stateDiagram-v2
[*] --> Requested: User logs in
Requested --> Validated: Validate First Token
Validated --> Created: Generate Second Token
+ Version (UUID)
Created --> Stored: Store in Redis & Frontend
(Token + Version)
state Stored {
[*] --> Active
Active --> VersionCheck: API Request
VersionCheck --> Active: Version Match
VersionCheck --> RefreshRequired: Version Mismatch
RefreshRequired --> Active: Token Refreshed
}
Stored --> InUse: API Requests
(with X-Token-Version)
InUse --> Stored: Request Complete
Stored --> Expired: TTL Reached
Stored --> Revoked: User Logout
Expired --> [*]
Revoked --> [*]
note right of Stored
Token + Version stored
Redis validates on each request
end note
note right of RefreshRequired
Backend sends
X-Token-Refresh-Required: true
end note
note right of Expired
Auto-removed from
Redis by TTL
end note
:::
📊 Lifecycle Phases
| Phase | Action | Location | Duration | Version Handling |
|-------|--------|----------|----------|------------------|
| 🎯 Creation | Generated post-validation | ERP Backend | Instant | UUID v4 generated |
| 💾 Storage | Dual persistence | Frontend + Redis | Synchronized | Both layers store version |
| 🚀 Active Use | API request authorization | All ERP endpoints | Until expiry | Version sent in header |
| 🔍 Validation | Version matching | Backend middleware | Per request | Compare frontend vs Redis |
| 🔄 Refresh | Forced renewal | On version mismatch | Immediate | New version generated |
| 🗑️ Revocation | Cleanup & invalidation | Both layers | On logout/expire | Version deleted |
🔄 Token Version Management
Version Generation
Format: UUID v4 (Universally Unique Identifier)
Example:
550e8400-e29b-41d4-a716-446655440000Generation: On each token creation/refresh
Uniqueness: Cryptographically random, collision-resistant
Redis Storage Structure
Key: erp:token:{userId}
Value: {
"token": "eyJhbGciOiJIUzI1NiIs...",
"version": "550e8400-e29b-41d4-a716-446655440000",
"createdAt": 1701349200,
"expiresAt": 1701352800
}
TTL: 3600 secondsFrontend Storage Structure
javascript
// SessionStorage
{
"erpToken": "eyJhbGciOiJIUzI1NiIs...",
"erpTokenVersion": "550e8400-e29b-41d4-a716-446655440000",
"tokenExpiresAt": 1701352800000
}Version Validation Flow
Frontend: Sends
X-Token-Versionheader with every API requestBackend: Retrieves current version from Redis
Comparison:
- ✅ Match: Process request normally
- ❌ Mismatch: Set X-Token-Refresh-Required: true in response
Frontend: Detects refresh header, requests new token automatically
Backend: Issues new token with new version, updates Redis
Error Handling
::: mermaid
flowchart TD
Start([User requests Second Token]) --> ValidateToken
ValidateToken -->|No| Unauthorized[Return 401 Unauthorized
Redirect to Login]
Unauthorized --> End1([End])
ValidateToken -->|Yes| GenerateToken{Generate Second Token
+ Version?}
GenerateToken -->|Failed| ServerError[Log Error
Return 500 Internal Server Error]
ServerError --> End2([End])
GenerateToken -->|Success| StoreRedis{Store Token + Version
in Redis?}
StoreRedis -->|Failed| ServiceError[Log Error
Return 503 Service Unavailable
Redis connection failed]
ServiceError --> End3([End])
StoreRedis -->|Success| Success[Return Token + Version
Status: 200 OK
X-Token-Version header]
Success --> End4([End])
style Unauthorized fill:#ffcccc
style ServerError fill:#ffcccc
style ServiceError fill:#ffcccc
style Success fill:#ccffcc
:::
Version Mismatch Handling
::: mermaid
flowchart TD
APIRequest([API Request with
X-Token-Version]) --> GetRedisVersion{Retrieve Version
from Redis}
GetRedisVersion -->|Redis Error| CacheError[Continue Processing
Log Warning]
CacheError --> ProcessRequest[Process Request]
ProcessRequest --> Return200[Return 200 OK]
GetRedisVersion -->|Success| CompareVersions{Frontend Version
== Redis Version?}
CompareVersions -->|Match| ProcessRequest2[Process Request]
ProcessRequest2 --> Return200_2[Return 200 OK]
Return200_2 --> End5([End])
CompareVersions -->|Mismatch| SetRefreshHeader[Process Request
Add Header:
X-Token-Refresh-Required: true]
SetRefreshHeader --> Return200Refresh[Return 200 OK
with Refresh Header]
Return200Refresh --> FrontendDetect[Frontend Detects Header]
FrontendDetect --> RequestNewToken[Request New Token
Automatically]
RequestNewToken --> End6([End])
Return200 --> End7([End])
style CacheError fill:#fff4cc
style SetRefreshHeader fill:#cce5ff
style RequestNewToken fill:#ccffcc
:::
🚨 Error Scenarios & Responses
| Error Type | HTTP Status | Client Action | Backend Action | Headers Set |
|------------|-------------|---------------|----------------|-------------|
| Invalid Keycloak Token | 401 Unauthorized | Redirect to login | Log security event | - |
| Token Generation Failed | 500 Internal Error | Show error message | Alert monitoring | - |
| Redis Unavailable | 503 Service Unavailable | Retry with backoff | Failover mechanism | - |
| Token Expired | 401 Unauthorized | Request new token | Clear Redis entry | - |
| Version Mismatch | 200 OK | Auto-refresh token | Continue processing | X-Token-Refresh-Required: true |
| Missing Version Header | 200 OK | Request continues | Log warning | X-Token-Refresh-Required: true |
| Network Timeout | 408 Request Timeout | Retry request | N/A | - |
📈 Performance Metrics
Token Generation: < 100ms
Version Generation (UUID): < 1ms
Redis Write/Read: < 10ms
Token Validation: < 50ms
Version Comparison: < 5ms
End-to-End Flow: < 500ms
Cache Hit Ratio: > 95%
🔧 Configuration
Recommended TTL Settings
Development: 1 hour (3600s)
Staging: 8 hours (28800s)
Production: 12 hours (43200s)
Redis Configuration
yaml
Key Pattern: erp:token:{userId}
Data Structure: Hash
Fields:
- token: JWT string
- version: UUID v4
- createdAt: Unix timestamp
- expiresAt: Unix timestamp
Eviction Policy: volatile-ttl
Max Memory: 2GB
Persistence: AOF enabled🎯 Benefits of Version Control
Security Benefits
✅ Prevents Stale Token Usage: Old cached tokens become invalid
✅ Session Invalidation: Force logout across all devices by changing version
✅ Concurrent Session Detection: Identify multiple sessions using same token
✅ Token Rotation: Seamless token refresh without breaking active sessions
Operational Benefits
✅ Zero-Downtime Updates: Can invalidate all tokens during critical updates
✅ Debugging: Track which token version is in use
✅ Audit Trail: Version changes logged for compliance
✅ Graceful Migration: Users automatically get new tokens without re-login