Java Multi-Device Login: Session-Centric Architecture for Device Control & Concurrency
This article details a session-centric Java architecture for multi-device login, covering device session creation, JWT with session IDs, single-device logout, admin forced logout, per-device-type concurrency control using Redis atomic operations, login limits with LRU eviction, device ID generation, refresh token binding, and security considerations for immediate token revocation.
From JWT-Only to Session-Centric Design
The author describes evolving a login system from a simple JWT-only model to a robust multi-device architecture. Initially, each login generated a JWT containing only the userId, making it impossible to distinguish devices or revoke individual sessions. The key insight: multi-device login requires managing LoginSession objects, not just tokens. JWT becomes a short-lived access credential issued per session.
LoginSession Data Model
A LoginSession record captures:
public record LoginSession(</code><code> String sessionId,</code><code> Long userId,</code><code> DeviceType deviceType,</code><code> String deviceId,</code><code> String deviceName,</code><code> String ip,</code><code> String userAgent,</code><code> SessionStatus status,</code><code> Instant loginAt,</code><code> Instant lastActiveAt</code><code>)On login, a session is created in Redis (e.g., auth:session:S_8e2f1c...) and the JWT includes a sid claim linking the token to that session.
Logout Current Device
Previously, logout only deleted the client-side token. Now, a POST /api/auth/logout endpoint revokes the session by setting its status to REVOKED. Subsequent requests with the same sid are rejected with 401.
Logout All Devices / Admin Force Logout
A Redis set auth:user:10086:sessions tracks all session IDs for a user. logoutAll(userId) iterates the set and revokes each session. Admin force-logout uses the same mechanism.
Per-Device-Type Concurrency Control (PC Only One)
Requirement: allow multiple mobile devices but only one PC. Implementation queries active PC sessions for the user and revokes them before creating the new session. However, a race condition exists if two PCs log in simultaneously.
Atomic Solution with Redis Lua
Maintain a key auth:current:10086:PC holding the current PC session ID. On new PC login, a Lua script atomically swaps the value and returns the old session ID, which is then revoked. This guarantees only one active PC session even under concurrent logins.
Configurable Login Limits (Max N Devices)
Two strategies when the limit is reached:
Reject the new login.
Evict the least recently used session (based on lastActiveAt).
To avoid excessive writes, lastActiveAt is updated only if the previous update was more than 5 minutes ago.
Device Identification
Web: generate a random UUID on first visit, store in localStorage, send with login request. Mobile: use Keychain (iOS) or Keystore (Android) for persistence across updates. The device ID is for UX and management, not a security boundary; authentication relies on credentials, refresh tokens, and sessions.
Device Management UI
GET /api/auth/sessionsreturns a list of sessions with device info, login time, last activity, and a current flag. Users can revoke non-current sessions via DELETE /api/auth/sessions/{id} after ownership verification. Admins can revoke any session.
Refresh Token Binding
Each LoginSession stores a hash of its refresh token. Access tokens carry the sid. This creates independent token pairs per device, enabling per-device revocation without affecting others.
Immediate Access Token Revocation Trade-offs
When a session is revoked, its refresh token is invalidated. For access tokens (e.g., 15-min TTL), two options:
Passive: Let access tokens expire naturally (up to 15 min delay). Simpler, no per-request session check.
Active: On each API request, check auth:session:{sid} status; if REVOKED, return 401 immediately. Adds a Redis lookup per request.
Choose based on security requirements: passive for content systems, active for admin/sensitive systems.
Redis Data Structure
Instead of a single login:user:10086 key, use:
Per-session keys: auth:session:S001, auth:session:S002...
User session index: auth:user:10086:sessions (Redis Set of session IDs).
Cleanup tasks remove expired/revoked sessions from the set.
Account Disable via Session Version
To instantly invalidate all sessions without iterating, maintain a userSessionVersion in the database. Each new session records the current version. On disable, increment the version; all sessions with the old version become invalid.
Remember Me Extends Session Lifetime, Not Access Token
Normal login: Access Token 15 min, Refresh Session 7 days. With "Remember Me": Access Token still 15 min, Refresh Session 30 days. This keeps the short-lived access token security boundary intact while extending the re-authentication interval.
Stranger Device Detection
Historical sessions provide context (device type, location). On login from a new device/region, send an alert email/push and optionally require 2FA (SMS, email, TOTP). IP changes alone are not reliable risk signals due to VPNs, mobile networks, NAT.
Data Minimization in Session List
The device management API returns only user-friendly data: parsed browser/OS, masked IP (e.g., 203.0.xxx.xxx), login/activity timestamps. Raw User-Agent, internal risk fields, and refresh token hashes are never exposed.
Unified Login Architecture
The final model:
User Authentication</code><code> ↓</code><code>LoginSession</code><code> ↓</code><code>┌─────────────────┐</code><code>│ Access Token │ │ Refresh Token │</code><code>│ API Access │ │ Silent Renewal │</code><code>└─────────────────┘All multi-device operations (logout, kick, limits, disable) become session state transitions. The session — not the token — is the central managed entity.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
