Authentication Documentation
This documentation explains the features and usage of:
- Authentication Module: Located at
src/modules/auth - Session Module: Located at
src/modules/session - ApiKey Module: Located at
src/modules/api-key
Overview
This document provides a comprehensive overview of authentication and session management in the Complete NestJS Boilerplate.
It covers:
- Password: Passwords are securely hashed (bcrypt), have configurable expiration and rotation, login attempt limits, history tracking, and support for reset/change/temporary password with session invalidation.
- JWT Authentication: Stateless authentication using access and refresh tokens with ES256/ES512 algorithms, configurable expiration, and security mechanisms such as JWT ID (jti) validation for session tracking.
- Session Management: Dual storage strategy using Redis for high-performance validation and automatic expiration, and database for session listing, management, and audit trail. Sessions are validated on every API request via jti matching and can be revoked instantly.
- Social Authentication: Integration with Google OAuth 2.0 and Apple Sign In, allowing users to authenticate using third-party providers. The backend validates OAuth tokens and manages sessions similarly to credential-based authentication.
- API Key Authentication: Stateless authentication for machine-to-machine and system integrations, supporting both default and system API keys with caching for performance.
Configuration for tokens, sessions, password, social providers, and API keys is managed in src/configs/auth.config.ts.
Related Documents
- Cache Documentation - For understanding session storage and caching mechanisms
- Configuration Documentation - For auth configuration details
- Environment Documentation - For JWT and OAuth environment variables
Table of Contents
- Overview
- Related Documents
- Password
- JWT Authentication
- Social Authentication
- Two-Factor Authentication (TOTP)
- API Key Authentication
- Session Management
Password
Secures passwords with bcrypt hashing, enforces expiration and rotation, tracks history, limits login attempts, and supports reset, change, and temporary password creation with session invalidation.
Password Configuration
All password settings are configured in src/configs/auth.config.ts:
export default registerAs(
'auth',
(): IConfigAuth => ({
password: {
// Enable/disable login attempt limiting feature
attempt: true,
// Maximum number of failed login attempts before user is inactivated
maxAttempt: 5,
// Length of salt used in bcrypt password hashing
saltLength: 8,
// Password expiration time in seconds (182 days = 15724800 seconds)
expiredInSeconds: 15724800,
// Temporary password expiration time in seconds (3 days = 259200 seconds)
expiredTemporaryInSeconds: 259200,
// Password rotation period in seconds (90 days = 7776000 seconds)
// Users are prompted to change password after this period
periodInSeconds: 7776000,
},
})
);Password Flow
JWT Authentication
JWT (JSON Web Token) is an open standard (RFC 7519 ) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.
JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
For more detailed information about JWT, please visit the official JWT website .
Note: Before using JWT authentication, you must generate cryptographic key pairs. See the Installation Documentation - Generate Keys section for detailed instructions on key generation.
Jwt Configuration
All JWT settings are configured in src/configs/auth.config.ts:
export default registerAs(
'auth',
(): IConfigAuth => ({
jwt: {
accessToken: {
// JWKS URI for token validation (optional, from environment)
jwksUri: process.env.AUTH_JWT_ACCESS_TOKEN_JWKS_URI,
// Key ID for JWKS (optional, from environment)
kid: process.env.AUTH_JWT_ACCESS_TOKEN_KID,
// Algorithm for signing and verifying access tokens
algorithm: 'ES256', // ECDSA using P-256 and SHA-256
// Private key for signing access tokens (from environment)
privateKey: process.env.AUTH_JWT_ACCESS_TOKEN_PRIVATE_KEY,
// Public key for verifying access tokens (from environment)
publicKey: process.env.AUTH_JWT_ACCESS_TOKEN_PUBLIC_KEY,
// Access token expiration time in seconds (1 hour = 3600 seconds)
expirationTimeInSeconds: 3600,
},
refreshToken: {
// JWKS URI for token validation (optional, from environment)
jwksUri: process.env.AUTH_JWT_REFRESH_TOKEN_JWKS_URI,
// Key ID for JWKS (optional, from environment)
kid: process.env.AUTH_JWT_REFRESH_TOKEN_KID,
// Algorithm for signing and verifying refresh tokens
algorithm: 'ES512', // ECDSA using P-521 and SHA-512
// Private key for signing refresh tokens (from environment)
privateKey: process.env.AUTH_JWT_REFRESH_TOKEN_PRIVATE_KEY,
// Public key for verifying refresh tokens (from environment)
publicKey: process.env.AUTH_JWT_REFRESH_TOKEN_PUBLIC_KEY,
// Refresh token expiration time in seconds (30 days = 2592000 seconds)
// This value also determines Redis session TTL
expirationTimeInSeconds: 2592000,
},
// JWT audience claim (identifies intended recipients)
audience: process.env.AUTH_JWT_AUDIENCE,
// JWT issuer claim (identifies who issued the token)
issuer: process.env.AUTH_JWT_ISSUER,
// HTTP header name for token transmission
header: 'Authorization',
// Token prefix (e.g., 'Bearer' in 'Bearer <token>')
prefix: 'Bearer',
},
})
);JWT Flow
JWT Access Token Flow
The following diagram illustrates the complete authentication flow from login to token generation:
JWT Refresh Token Flow
When the access token expires, the refresh token is used to obtain a new access token. The jti validation ensures additional security by tracking token usage:
JWT Tokens
JWT Access Token
A short-lived token used to authenticate API requests.
- Algorithm: ES256 (ECDSA using P-256 and SHA-256)
- Validity: Configured in
auth.config.ts(default: 1 hour) - Config:
AUTH_JWT_ACCESS_TOKEN_EXPIREDenvironment variable - Purpose: Authenticate API requests
- jti: Included for session tracking and validation
JWT Refresh Token
A long-lived token used to obtain new access tokens without requiring the user to log in again.
- Algorithm: ES512 (ECDSA using P-521 and SHA-512)
- Validity: Configured in
auth.config.ts(default: 30 days) - Config:
AUTH_JWT_REFRESH_TOKEN_EXPIREDenvironment variable - Redis TTL: Session TTL in Redis follows this expiration time
- Purpose: Generate new access tokens without re-authentication
- jti: Included for session tracking and validation
JWT Payload Structure
JWT Access Token Payload
Interface IAuthJwtAccessTokenPayload
{
loginAt: Date;
loginFrom: EnumUserLoginFrom;
loginWith: EnumUserSignUpWith;
email: string;
username: string;
userId: string;
sessionId: string;
roleId: string;
// Standard JWT claims
jti?: string; // JWT ID - unique token identifier
iat?: number; // Issued at
nbf?: number; // Not before
exp?: number; // Expiration time
aud?: string; // Audience
iss?: string; // Issuer
sub?: string; // Subject
}JWT Refresh Token Payload
Interface IAuthJwtRefreshTokenPayload
{
loginAt: Date;
loginFrom: EnumUserLoginFrom;
loginWith: EnumUserSignUpWith;
userId: string;
sessionId: string;
// Standard JWT claims
jti?: string; // JWT ID - unique token identifier
iat?: number;
nbf?: number;
exp?: number;
aud?: string;
iss?: string;
sub?: string;
}Usage
Protecting Endpoints
To protect an endpoint with JWT access token validation, use the @AuthJwtAccessProtected decorator:
@AuthJwtAccessProtected()
@Get('/profile')
async getProfile() {
// This endpoint requires a valid access token
// Token signature (ES256) is verified
// Session existence is validated in Redis
// jti is validated against cached session
return { message: 'Profile data' };
}For refresh token endpoints (typically only used in the refresh endpoint itself), use @AuthJwtRefreshProtected:
@AuthJwtRefreshProtected()
@Post('/refresh')
async refresh() {
// This endpoint requires a valid refresh token
// Token signature (ES512) is verified
// jti is validated against cached session
return { message: 'Token refreshed' };
}Getting JWT Payload
To access the JWT payload in your controller, use the @AuthJwtPayload() decorator:
@AuthJwtAccessProtected()
@Get('/me')
async getCurrentUser(
@AuthJwtPayload() payload: IAuthJwtAccessTokenPayload
) {
// Access user information from token
return {
userId: payload.userId,
email: payload.email,
username: payload.username,
sessionId: payload.sessionId,
roleId: payload.roleId
};
}You can also extract specific fields:
@AuthJwtAccessProtected()
@Get('/user-id')
async getUserId(
@AuthJwtPayload('userId') userId: string
) {
return { userId };
}Getting Raw Token
To access the raw JWT token string, use the @AuthJwtToken() decorator:
@AuthJwtAccessProtected()
@Get('/verify')
async verifyToken(
@AuthJwtToken() token: string
) {
// Access raw token for additional processing
return { token };
}Security: JWT ID (jti)
The JWT ID (jti) is a critical security mechanism for both access and refresh token validation.
A unique identifier (32-character random string) generated during login and token refresh, stored in both the token payload and the session in Redis.
How it Works
-
During Login
- API generates a unique jti (32-character random string)
- jti is stored in Redis session
- jti is embedded in both access and refresh tokens as a standard JWT claim
-
During Every API Request (Access Token)
- Client sends request with access token
- API extracts the jti from the refresh token payload
- API retrieves session from Redis using userId and sessionId
- API compares token jti with session jti
- If jti matches: Token refresh proceeds with a new jti
- If jti doesn’t match: Request is rejected (401 Unauthorized - potential security breach)
-
jti Rotation
- Each successful token refresh generates a new jti (32-character random string)
- Old jti is invalidated
- New jti is stored in Redis
- New jti is stored in database session record
- New tokens contain the new jti
- If fingerprints don’t match: Request is rejected (potential security breach)
-
Security Benefits
- Token Reuse Detection: If an old access/refresh token is used after refresh, the jti won’t match
- Session Tracking: Each token refresh creates a new jti, allowing precise tracking of token usage
- Instant Revocation: Deleting the session from Redis immediately invalidates all tokens with that sessionId
- Replay Attack Prevention: Old tokens cannot be reused even if intercepted
-
Fingerprint Rotation
- Each successful token refresh generates a new fingerprint
- Old fingerprint is invalidated
- New fingerprint is stored in Redis
- New tokens contain the new fingerprint
- Important: Session TTL remains unchanged (stays at initial value from login based on
AUTH_JWT_REFRESH_TOKEN_EXPIREDconfig)
Social Authentication
Social authentication allows users to sign in using their Google or Apple accounts. The backend validates the OAuth tokens provided by the client and extracts user information to create a session, similar to credential-based authentication.
Supported Providers:
- Google OAuth 2.0
- Apple Sign In
Social Authentication Flow
The following diagram illustrates the social authentication flow:
Google Authentication
Configuration
Google authentication is configured in auth.config.ts:
export default registerAs(
'auth',
(): IConfigAuth => ({
google: {
header: 'Authorization',
prefix: 'Bearer',
clientId: process.env.AUTH_SOCIAL_GOOGLE_CLIENT_ID,
clientSecret: process.env.AUTH_SOCIAL_GOOGLE_CLIENT_SECRET,
}
})
);Environment Variables:
AUTH_SOCIAL_GOOGLE_CLIENT_ID: Google OAuth 2.0 client IDAUTH_SOCIAL_GOOGLE_CLIENT_SECRET: Google OAuth 2.0 client secret
Setup Google OAuth 2.0
To obtain Google OAuth credentials:
- Go to Google Cloud Console
- Create a new project or select existing project
- Enable Google+ API
- Create OAuth 2.0 credentials (Web application)
- Configure authorized redirect URIs
- Copy Client ID and Client Secret to your
.envfile
For detailed setup instructions, visit Google OAuth 2.0 Documentation
Usage
Protecting the Endpoint:
@AuthSocialGoogleProtected()
@Post('/login/social/google')
async loginGoogle(@AuthJwtPayload() payload: IAuthSocialPayload) {
const { email, emailVerified } = payload;
// Find or create user
// Generate session with jti
// Return JWT tokens (both include jti)
}Apple Authentication
Configuration
Apple authentication is configured in auth.config.ts:
export default registerAs(
'auth',
(): IConfigAuth => ({
apple: {
header: 'Authorization',
prefix: 'Bearer',
clientId: process.env.AUTH_SOCIAL_APPLE_CLIENT_ID,
signInClientId: process.env.AUTH_SOCIAL_APPLE_SIGN_IN_CLIENT_ID,
}
})
);Environment Variables:
AUTH_SOCIAL_APPLE_CLIENT_ID: Apple service IDAUTH_SOCIAL_APPLE_SIGN_IN_CLIENT_ID: Apple sign-in client ID
Setup Apple Sign In
To obtain Apple credentials:
- Go to Apple Developer Portal
- Create an App ID with Sign in with Apple capability
- Create a Services ID for web authentication
- Configure return URLs
- Download and configure private key
- Copy Service ID (Client ID) to your
.envfile
For detailed setup instructions, visit Apple Sign In Documentation
Usage
Protecting the Endpoint:
@AuthSocialAppleProtected()
@Post('/login/social/apple')
async loginApple(@AuthJwtPayload() payload: IAuthSocialPayload) {
const { email, emailVerified } = payload;
// Find or create user
// Generate session with jti
// Return JWT tokens (both include jti)
}Two-Factor Authentication (TOTP)
TOTP-based 2FA adds a second verification step to login. Tokens are only issued after the user passes 2FA.
Flow
See Two-Factor Documentation for detailed.
API Key Authentication
API Key authentication provides a simple, stateless authentication mechanism for machine-to-machine communication and system integrations. Unlike JWT tokens, API keys don’t require session management and are validated directly against the database/cache.
Use Cases:
- External system integrations
- Webhook endpoints
- System-to-system communication
- Background jobs and scheduled tasks
- Third-party API access
Configuration
API Key authentication is configured in auth.config.ts:
export default registerAs(
'auth',
(): IConfigAuth => ({
xApiKey: {
header: 'x-api-key',
cachePrefixKey: 'ApiKey',
},
})
);Configuration Options:
header: Header name for API key (x-api-key)cachePrefixKey: Redis cache prefix for API key caching
API Key Types
Default API Key
Default API keys are used for standard external integrations and third-party access.
Characteristics:
- Type:
EnumApiKeyType.default - Purpose: General-purpose API access
- Use Case: External clients, third-party integrations
- Validation: Requires valid
key:secretcombination - Cache: Cached in Redis for performance
Guard Decorator:
@ApiKeyProtected()Example Usage:
@ApiKeyProtected()
@Get('/api/external/data')
async getExternalData(@ApiKeyPayload() apiKey: ApiKey) {
return { data: 'accessible with default API key' };
}System API Key
System API keys are used for internal system operations that bypass standard authentication.
Characteristics:
- Type:
EnumApiKeyType.system - Purpose: System-level operations
- Use Case: Internal services, background jobs, system maintenance
- Validation: Requires valid
key:secretcombination - Cache: Cached in Redis for performance
Guard Decorator:
@ApiKeySystemProtected()Example Usage:
@ApiKeySystemProtected()
@Post('/api/system/maintenance')
async runMaintenance(@ApiKeyPayload() apiKey: ApiKey) {
// System-level endpoint
// No user authentication required
return { status: 'maintenance completed' };
}Request Format
API keys are sent via the x-api-key header with the format ${key}:${secret}:
Header Format:
x-api-key: ${key}:${secret}Format Rules:
- Pattern:
key:secret - Separator: Colon (
:) - Both key and secret are required
- No spaces allowed
- Case-sensitive
Usage
Protecting Endpoints
Default API Key Protection:
@ApiKeyProtected()
@Get('/external/data')
async getExternalData(@ApiKeyPayload() apiKey: ApiKey) {
// Endpoint requires default API key
// apiKey contains full API key schema from database
return {
message: 'Data accessed with default API key',
apiKeyId: apiKey.id,
apiKeyName: apiKey.name
};
}System API Key Protection:
@ApiKeySystemProtected()
@Post('/system/maintenance')
async runMaintenance(@ApiKeyPayload() apiKey: ApiKey) {
// Endpoint requires system API key
// Bypasses user authentication
// Used for system-level operations
return {
message: 'Maintenance task executed',
executedBy: apiKey.name
};
}Getting API Key Payload
Access the full API key data using @ApiKeyPayload() decorator:
Full Payload:
@ApiKeyProtected()
@Get('/resource')
async getResource(@ApiKeyPayload() apiKey: ApiKey) {
// Access full API key object
return {
keyId: apiKey.id,
keyName: apiKey.name,
keyType: apiKey.type,
isActive: apiKey.isActive
};
}Specific Fields:
@ApiKeyProtected()
@Get('/resource')
async getResource(
@ApiKeyPayload('name') apiKeyName: string,
@ApiKeyPayload('type') apiKeyType: EnumApiKeyType
) {
// Extract specific fields only
return {
accessedBy: apiKeyName,
keyType: apiKeyType
};
}API Key Authentication Flow
Session Management
Session management handles user authentication sessions across multiple devices and locations. It provides visibility and control over active sessions, allowing users and administrators to monitor and revoke access as needed.
This implementation uses a dual storage strategy:
- Redis: High-performance session validation and automatic expiration
- Database: Session listing, management, and audit trail
Session Storage
Redis (Primary - Validation)
Used for high-speed session validation for both access and refresh tokens.
Critical Behavior: Every API call with an access token will check Redis. If the session is not found in Redis or the jti doesn’t match, the request is rejected immediately, even if the token signature is valid.
Data Stored:
{
sessionId: string;
userId: string;
jti: string; // JWT ID for token validation
expiredAt: Date;
}Redis Key Pattern:
User:{userId}:Session:{sessionId}TTL Behavior:
- Initial TTL: Follows refresh token expiration from
auth.config.ts(default: 30 days) - TTL Source:
AUTH_JWT_REFRESH_TOKEN_EXPIREDenvironment variable - TTL Behavior: NOT extended on token refresh - remains at initial value from login
- Auto Cleanup: Expired sessions are automatically removed by Redis when TTL expires
Example:
- If
AUTH_JWT_REFRESH_TOKEN_EXPIRED=30d, Redis TTL = 30 days - If
AUTH_JWT_REFRESH_TOKEN_EXPIRED=7d, Redis TTL = 7 days - Token refresh updates jti but does NOT reset the TTL
Database (Secondary - Management)
Used for session listing and management purposes.
When Updated:
- Created during login with initial jti
- Updated when session jti is rotated during token refresh
- Updated when session is revoked
- Can be queried to show user’s active sessions across devices
Not Used For: A[User Login] —> B[Create Session in Database with jti] A —> C[Create Session in Redis with jti and TTL 30d]
How They Work Together
What Happens on Revocation
When a session is revoked:
- Redis: Session is deleted immediately
- Database: Session record is updated (marked as revoked)
-
- Access Tokens: All access tokens for this session become invalid immediately (jti validation fails)
- Refresh Tokens: All refresh tokens for this session become invalid immediately (jti validation fails)
- Active Requests: Any subsequent API calls with tokens from this session will be rejected with 401 Unauthorized
Session Lifecycle
-
Creation (Login)
- User logs in successfully
- System generates unique sessionId and jti
- Session stored in both Redis (with TTL) and Database (with jti)
- Tokens issued containing sessionId and jti
-
Validation (Every Request)
- Access token received
- Token signature verified (ES256)
- sessionId and jti extracted from token
- Redis checked for session existence
- jti compared between token and Redis session
- Request allowed only if session exists AND jti matches
-
Refresh
- Refresh token received
- Token signature verified (ES512)
- sessionId and jti extracted from token
- Redis checked for session existence
- jti compared between token and Redis session
- If valid: new jti generated, session updated in Redis and Database
- New tokens issued with new jti
- Old jti invalidated (old tokens won’t work)
-
Expiration
- Redis TTL expires (based on config)
- Session automatically removed from Redis
- All tokens become invalid (session not found in Redis)
- Database record remains for audit trail
-
Revocation
- User or admin revokes session
- Session deleted from Redis immediately
- Database record marked as revoked
- All tokens for this session become invalid immediately