Appearance
Token Management
DaraMex uses a secure JWT-based authentication system with automatic refresh token rotation, strict session validation, and real-time reuse detection.
Token Pair Properties
Each session is represented by a pair of JSON Web Tokens (JWTs):
| Token | Access Token | Refresh Token |
|---|---|---|
| Duration | Short-lived (e.g., 1h). | Long-lived (e.g., 7d). |
| Purpose | Authenticates individual requests via Authorization: Bearer <token>. | Used once to request a new token pair. |
| Storage | Client memory (recommended) or short-lived cookie. | Persistent client storage (e.g., localStorage) or long-lived cookie. |
| Payload | sub, type (user|client), agencyId, sessionId (required). | sub, type, agencyId, sessionId (required), jti (UUIDv7 unique token ID). |
Session Validation (Real-Time Revocation)
Access tokens contain a sessionId. On every protected request, the API verifies that the associated session is still active (session.isActive === true). This ensures that if a session is revoked (e.g., due to a password change, manual revocation, or suspected token theft), the access token is immediately invalidated, even if its JWT expiration time has not yet passed.
Refresh Token Rotation & Reuse Detection
To minimize the risk of token theft, we implement Refresh Token Rotation. When a refresh token is used, it is invalidated and a brand-new pair is issued.
- Stored Token: Each issued refresh token's
jti(generated via UUIDv7 for sequential indexing) is hashed and stored in the database. - Rotation: Upon request to
/auth/refresh, the system verifies the provided token's signature, expiration, and associated session validity. - Atomic Invalidation: The system atomically attempts to mark the token as
revokedAt. - Reuse Detection: If the database reports the token was already revoked, a token theft event is assumed. The system immediately invalidates all active sessions for that subject, logs a warning, and returns a dedicated error code (
IDENTITY.REFRESH_TOKEN_REUSE_DETECTED) with a neutral re-authentication message. - Session-Preserving Rotation: Successful refresh rotation always issues the new token pair under the same session referenced by the incoming refresh token.
The token service (TokenPort / TokenAdapter) uses Result<T> across all operations (generateTokenPair, generateTokenPairForSession, refreshTokens, revokeSession, revokeAllForSubject). Refresh logic does not throw custom exceptions for reuse detection; instead it returns Result.fail(IdentityErrors.refreshTokenReuseDetected(...)), and the command handler forwards that structured app error.
sequenceDiagram
participant Client as Frontend
participant API as AuthController
participant DB as Identity Database
Client->>API: POST /auth/refresh {refreshToken}
API->>API: Verify signature & Session Active status
API->>DB: Atomic Update: set revokedAt where tokenHash = ...
alt Token already revoked (Reuse Detected)
API->>DB: REVOKE ALL sessions for subject
API-->>Client: 401 Unauthorized (Reuse Detected)
else Token validly rotated
API->>API: Generate NEW Token Pair (UUIDv7 jti)
API->>DB: Save NEW RefreshToken hash
API-->>Client: 200 OK (New Tokens)
end
Logout Logic
Logging out is session-centered and authenticated.
- The client calls
POST /auth/logoutusing a valid access token. - The API reads
sessionIdfrom authenticated context. - The API revokes the current session.
- The API revokes all active refresh tokens linked to that session.
Password Change Token Rotation
Password changes preserve the current session but rotate its refresh credentials.
- The API updates the password hash.
- The API revokes all other active sessions and their refresh tokens.
- The API revokes refresh tokens for the current session.
- The API issues a new token pair for the same current session ID.
- The endpoint returns the fresh
accessTokenandrefreshTokento the caller.
Data Structure: Refresh Token Entity
typescript
type RefreshTokenProps = {
tokenHash: string; // Hash of the jti (UUIDv7)
subjectId: string; // User ID or Client ID
subjectType: SubjectType; // 'user' | 'client'
sessionId: string; // Required link to the active session
agencyId: string | null;
expiresAt: Date;
revokedAt: Date | null;
};