Architecture
Configuration (src/lib/auth.ts)
The Auth.js configuration lives in src/lib/auth.ts and exports { handlers, auth, signIn, signOut }.
Session Strategy
Custom JWT Claims
Thejwt and session callbacks propagate three pension-specific fields from the user record into every token and session:
| Claim | Type | Purpose |
|---|---|---|
role | UserRole | Drives RBAC throughout the application |
brokerId | string | null | Scopes data queries to the correct broker |
employerId | string | null | Scopes data queries to the correct employer |
Password Hashing
Passwords are hashed with bcryptjs before storage. On authentication,bcrypt.compare() is used — timing-safe by design. Plaintext passwords are never logged, stored, or transmitted.
Login Page
Auth.js is configured to redirect unauthenticated requests to/auth/login:
Auth Flow
Credential submission
User submits email + password to
POST /api/auth/signin (handled by Auth.js route handler at src/app/api/auth/[...nextauth]/route.ts).Database lookup
Drizzle ORM queries the
users table by email. If DATABASE_URL is not set, the request fails in production.Password verification
bcrypt.compare(providedPassword, storedHash) — returns false if the hash does not match. Wrong passwords do not fall through to any fallback.JWT issuance
On success, Auth.js issues a signed JWT containing
id, role, brokerId, and employerId. The JWT secret is AUTH_SECRET (required environment variable).Role Mapping
The database schema uses granular role names; Auth.js maps these to application-levelUserRole values:
| Schema Role | Application Role |
|---|---|
SuperAdmin | admin |
BrokerAdmin | broker |
BrokerUser | broker |
Trustee | employer |
Development vs Production
In production:DATABASE_URLmust be set — the DB fallback is the only auth pathAUTH_SECRETmust be a cryptographically random string (minimum 32 bytes)- The dev credential fallback returns
nullimmediately
Environment Variables
| Variable | Required | Description |
|---|---|---|
AUTH_SECRET | ✅ | Signs and verifies JWTs. Generate with openssl rand -hex 32. |
AUTH_URL | ✅ Production | Canonical URL for Auth.js callbacks |
DATABASE_URL | ✅ Production | Neon PostgreSQL connection string |
TypeScript Augmentation
Custom session fields are typed via module augmentation insrc/lib/auth-types.ts, ensuring TypeScript catches any access to undefined session properties at compile time.