JWT & Session Management
Relevant source files
The following files were used as context for generating this wiki page:
The JWT & Session Management system in Subito provides a robust, multi-layered security framework for authenticating users and managing their active states across multiple devices. It combines stateless JSON Web Tokens (JWT) for authorization with a stateful session management layer backed by Redis and PostgreSQL.
This system handles token issuance, rotation (Refresh Token Rotation), revocation, and device-bound security. It supports three primary roles: customer, partner, and admin, each with specific permission sets determined during the token generation process.
Token Architecture
Subito utilizes a dual-token strategy involving short-lived Access Tokens and long-lived Refresh Tokens.
Access Tokens
Access tokens are signed using the HS256 algorithm and contain the user's identity, role, session ID, and specific permissions.
Sources: [apps/api/src/lib/jwt.ts:64-88](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/lib/jwt.ts#L64-L88)
Refresh Tokens
Refresh tokens are used to obtain new access tokens without requiring the user to re-authenticate via OTP. To prevent reuse attacks, the system implements Refresh Token Rotation and stores hashes of active tokens in the database.
Sources: [apps/api/src/lib/jwt.ts:90-104](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/lib/jwt.ts#L90-L104), [apps/api/src/modules/auth/auth.service.ts:316-330](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/modules/auth/auth.service.ts#L316-L330)
Token Configuration
| Parameter | Default Value | Environment Variable |
|---|---|---|
| Access Token Expiry | 15 minutes | ACCESS_TOKEN_EXPIRY |
| Refresh Token Expiry | 30 days | REFRESH_TOKEN_EXPIRY |
| JWT Issuer | subito-api | JWT_ISSUER |
| JWT Audience | subito-app | JWT_AUDIENCE |
Sources: [apps/api/src/lib/jwt.ts:16-24](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/lib/jwt.ts#L16-L24)
Session Management Logic
The session system tracks active user logins in Redis for high-performance "touching" (activity updates) and in PostgreSQL for persistence.
Session Lifecycle
- Creation: A session is created upon successful OTP verification. A unique
sessionIdis generated and linked to the user and their device. - Tracking: Each request "touches" the session in Redis, updating the
lastActivitytimestamp. To optimize performance, this update is throttled to once every 5 minutes. - Limits: A maximum of 5 sessions is allowed per user. The system automatically evicts the oldest session when this limit is reached.
- Revocation: Sessions can be revoked individually (logout) or globally (logout from all devices/password change).
Sources: [apps/api/src/lib/session.ts:18-103](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/lib/session.ts#L18-L103), [apps/api/src/modules/auth/auth.service.ts:373-396](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/modules/auth/auth.service.ts#L373-L396)
Session Data Flow
The following diagram illustrates the lifecycle of a session from creation to validation.
Sources: [apps/api/src/lib/session.ts:54-85](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/lib/session.ts#L54-L85), [apps/api/src/middleware/auth.ts:25-34](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/middleware/auth.ts#L25-L34)
Authorization & Permissions
Access control is enforced via middleware that validates tokens and checks for required roles or permissions.
Role-Based Permissions
Permissions are dynamically assigned based on the user's role at the time of token generation:
- Customer:
read:profile,create:booking,read:booking,read:listing,read:cart - Partner:
read:profile,read:partner_bookings,update:partner_status,update:partner_location - Admin:
*(All permissions)
Sources: [apps/api/src/lib/jwt.ts:33-52](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/lib/jwt.ts#L33-L52)
Auth Middleware Flow
The requireAuth middleware performs several critical security checks before allowing a request to proceed.
Sources: [apps/api/src/middleware/auth.ts:13-55](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/middleware/auth.ts#L13-L55)
Data Schema
The database maintains the persistent state for refresh tokens and user roles.
refresh_tokens Table
| Field | Type | Description |
|---|---|---|
id | uuid | Primary Key |
user_id | uuid | Reference to the users table |
token | text | SHA-256 hash of the refresh token |
session_id | uuid | Links the token to a Redis session |
device_id | varchar | Associated hardware identifier |
expires_at | timestamp | Token expiration date |
Sources: [packages/db/drizzle/0000_normal_the_order.sql:23-31](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/packages/db/drizzle/0000_normal_the_order.sql#L23-L31)
Security Features
Refresh Token Rotation
When a refresh token is used to generate a new access token, the old refresh token is deleted, and a new one is issued. If a token is reused (indicating a potential theft), the entire "token family" (all tokens associated with that sessionId) is revoked immediately.
Sources: [apps/api/src/modules/auth/auth.service.ts:254-269](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/modules/auth/auth.service.ts#L254-L269), [apps/api/src/modules/auth/auth.service.ts:316-335](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/modules/auth/auth.service.ts#L316-L335)
Token Blacklisting
During logout, access tokens (which are otherwise stateless) are hashed and stored in Redis with a TTL matching their remaining lifespan. The middleware checks this blacklist for every request.
Sources: [apps/api/src/modules/auth/auth.service.ts:340-353](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/modules/auth/auth.service.ts#L340-L353), [apps/api/src/middleware/auth.ts:20-23](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/middleware/auth.ts#L20-L23)
Device Binding
Tokens include a deviceId. If the X-Device-ID header provided by the client does not match the one embedded in the JWT, the request is rejected with a 401 Unauthorized error.
Sources: [apps/api/src/middleware/auth.ts:37-48](https://github.com/Sin7sterSPD/subito/blob/f39930e8433437d7fbf1d216c4408b9d42c76787/apps/api/src/middleware/auth.ts#L37-L48)
Conclusion
The Subito authentication system ensures high security through the combination of JWT for efficiency and stateful sessions for control. By implementing features like Refresh Token Rotation, device binding, and session limits, the project provides a resilient architecture against common authentication vulnerabilities while maintaining a seamless user experience across devices.