JWT and Session Authentication
JWT Bearer Tokens
Token Issuance
JWTs are issued upon successful authentication via the following endpoints:
| Endpoint | Trigger |
|---|---|
POST /api/public/signin |
Email + password authentication |
POST /api/public/mfa/verify |
MFA completion (TOTP code) |
POST /api/public/oauth/google |
Google OAuth credential exchange |
Token Structure
Algorithm: HS256 (HMAC-SHA256)
Claims:
{
"sub": "<user-id>",
"tenant_id": "<tenant-uuid>",
"role": "admin | user | regulator",
"email": "<user-email>",
"iat": 1708300000,
"exp": 1708303600
}
Lifetimes:
- Access token: 1 hour
- Refresh token: 7 days
Token Refresh
Clients exchange a valid refresh token for a new access token via POST /api/public/refresh. The endpoint issues both a new access token and a new refresh token.
Token Blacklisting
When a user's tokens are revoked (password change, admin action, or explicit logout), all tokens issued before the revocation timestamp are rejected. Blacklist checking occurs after signature verification.
POST /api/v1/admin/revoke-user-tokens
Body: { "userId": "<uuid>", "reason": "Password changed" }
The blacklist is stored in the token_blacklist table. Verification checks isTokenRevoked(userId, issuedAt) against the repository.
Key Rotation
The platform supports zero-downtime key rotation via two environment variables:
| Variable | Purpose |
|---|---|
JWT_SECRET |
Current signing and verification key (minimum 32 characters) |
JWT_SECRET_PREV |
Previous key, accepted for verification only |
During rotation, new tokens are signed with JWT_SECRET while tokens signed with the previous key remain valid until expiration.
Implemented in packages/auth/src/jwt.ts at commit 4b572c2.
Multi-Factor Authentication (MFA)
Setup Flow
- Client calls
POST /api/public/mfa/setup(requires Bearer token) - Server generates TOTP secret and returns
{ secret, otpauthUri } - User scans QR code in authenticator app
- Client calls
POST /api/public/mfa/verify-setupwith TOTP code to confirm enrollment
Signin with MFA
- Client authenticates via
POST /api/public/signin - If MFA is enabled, server returns an intermediate MFA challenge (5-minute TTL)
- Client submits TOTP code to
POST /api/public/mfa/verify - On success, server issues full JWT + refresh token
MFA tokens are one-time-use, enforced via a distributed store (Redis or in-memory fallback).
Implemented in services/api-gateway/src/routes/auth.ts at commit 4b572c2.
Session Cookie Authentication
Cookie Properties
| Property | Value |
|---|---|
| Name | elysium_session |
| HttpOnly | true |
| Secure | true |
| SameSite | none |
| Partitioned | true |
| Max-Age | 7 days |
Session Flow
- User authenticates via signin, MFA verification, or Google OAuth
- Server sets
elysium_sessioncookie alongside the JWT response - On subsequent requests, cookie middleware runs before the auth middleware
- Cookie middleware extracts and validates the session, populating
req.tenantId,req.user.userId,req.user.role, andreq.user.email - Session-authenticated users receive
nullAPI key scopes (unrestricted access equivalent to admin)
Cookie vs. JWT
Both mechanisms coexist. The auth middleware checks for cookie-based pre-authentication first. If a valid session cookie is present, JWT/API key validation is skipped.
Implemented in packages/auth/src/cookie-middleware.ts and packages/auth/src/middleware.ts at commit 4b572c2.
Google OAuth
Flow
- Client obtains Google OAuth credential via Google Sign-In
- Client sends credential to
POST /api/public/oauth/google - Server verifies credential via Google client library
- If user does not exist, server auto-provisions tenant and user based on email domain
- Server issues backend JWT + session cookie
Auto-Provisioning
Google OAuth supports automatic user and tenant creation. Users signing in via Google do not require pre-registration or invite tokens. A new tenant is created per unique email domain.
Implemented in services/api-gateway/src/routes/auth.ts (lines 1066-1266) at commit 4b572c2.