import { JwtConfig } from '../interfaces/config.interface'; /** * JWT Payload structure */ export interface JwtPayload { /** User ID */ sub: string; /** User email */ email: string; /** Token type (access or refresh) */ type: 'access' | 'refresh'; /** Session ID */ sessionId: string; /** Token family ID (for rotation detection) */ tokenFamily?: string; /** Issued at timestamp */ iat: number; /** Expiration timestamp */ exp: number; /** Issuer */ iss?: string; /** Audience */ aud?: string | string[]; /** Device ID */ deviceId?: string; } /** * Token validation result */ export interface TokenValidationResult { /** Whether token is valid */ valid: boolean; /** Decoded payload if valid */ payload?: JwtPayload; /** Error message if invalid */ error?: string; /** Error type for specific handling */ errorType?: 'expired' | 'invalid' | 'malformed' | 'blacklisted'; } /** * Token pair (access + refresh) */ export interface TokenPair { /** Short-lived access token */ accessToken: string; /** Long-lived refresh token */ refreshToken: string; /** Access token expiration in seconds */ expiresIn: number; } /** * JWT Service (Platform-Agnostic) * * Handles all JWT token operations using jose library for platform independence. * * **Features:** * - Platform-agnostic (no framework dependencies) * - Support for multiple algorithms (HS256, HS384, HS512, RS256, RS384, RS512) * - Token rotation with family tracking * - Token reuse detection * - Symmetric and asymmetric key support * * **Security Features:** * - HS256 as default algorithm (symmetric key) * - HS256/HS384/HS512 for symmetric keys * - RS256/RS384/RS512 for asymmetric keys * - Token rotation on refresh * - Token family tracking for reuse detection * - Configurable expiration times * - Standard JWT claims (iss, aud, sub, exp, iat) * * @example * ```typescript * const jwtService = new JwtService(config); * * // Generate token pair * const tokens = await jwtService.generateTokenPair({ * userId: 'user-123', * email: 'user@example.com', * sessionId: 'session-456', * }); * * // Validate token * const result = await jwtService.validateAccessToken(tokens.accessToken); * if (result.valid) { * console.log('User ID:', result.payload.sub); * } * ``` */ export declare class JwtService { /** JWT configuration */ private readonly config; /** Cached access token key (for performance) */ private accessTokenKey; /** * Cached access-token public key for verification (RS*) * * WHY: * - `crypto.createPublicKey(pem)` is synchronous and relatively expensive. * - Parsing the PEM on every request adds unnecessary CPU overhead and increases event-loop contention. * * NOTE: * - We intentionally cache the parsed KeyObject (static config) rather than re-parsing per request. * - If parsing fails, we store the error message and fail validation deterministically without repeated parsing. */ private accessTokenPublicKey; /** * Cached parse error for accessToken.publicKey (if invalid) * * Kept as a string to avoid leaking complex Error objects across boundaries. */ private accessTokenPublicKeyError; /** Cached refresh token key (for performance) */ private refreshTokenKey; /** * Cached jose module load. * Kept as a promise so concurrent calls share the same module load. */ private joseModulePromise; constructor(jwtConfig: JwtConfig); /** * Load jose in a way that works for both ESM and CommonJS consumers. * * "Proper" usage per jose docs: * - ESM: `import * as jose from 'jose'` * - CJS: `const jose = require('jose')` (when `require(esm)` is supported/enabled) * * In some production setups, `require()` is wrapped/intercepted (e.g. PM2), which can * break `require(esm)` and surface `ERR_REQUIRE_ESM`. In that case we fall back to a * native dynamic import so Node's ESM loader can resolve jose. * * @private */ private getJose; /** * Prepare and cache signing keys for better performance * @private */ private prepareKeys; /** * Get algorithm for signing access tokens * * Automatically selects appropriate algorithm based on key material: * - If privateKey is provided → uses configured algorithm (RS256, RS384, RS512) * - If only secret is provided → uses configured algorithm or defaults to HS256 * * @private */ private getAlgorithm; /** * Get algorithm for signing refresh tokens * * Refresh tokens only support symmetric algorithms (HS256/HS384/HS512) * because RefreshTokenConfig only provides a secret, not a privateKey. * * Automatically selects appropriate symmetric algorithm: * - If configured algorithm is symmetric (HS256/HS384/HS512) → uses it * - If configured algorithm is asymmetric (RS256, RS384, RS512) → falls back to HS256 * - Defaults to HS256 if no algorithm is configured * * @private */ private getRefreshTokenAlgorithm; /** * Generate both access and refresh tokens * * Creates a pair of tokens with the same token family for rotation tracking. * The token family allows detection of token reuse attacks. * * @param data - User and session information * @returns Token pair with access and refresh tokens * * @example * ```typescript * const tokens = await jwtService.generateTokenPair({ * userId: 'user-123', * email: 'user@example.com', * sessionId: 'session-456', * }); * * // Store tokens and send to client * res.json({ * accessToken: tokens.accessToken, * refreshToken: tokens.refreshToken, * expiresIn: tokens.expiresIn, * }); * ``` */ generateTokenPair(data: { userId: string; email: string; sessionId: string; tokenFamily?: string; /** * Optional per-request override for the refresh token's expiresIn. * When unset, falls back to config.refreshToken.expiresIn. Used by * hybrid-policy resolution to issue different refresh TTLs per * delivery mode. */ refreshExpiresIn?: string | number; }): Promise; /** * Generate an access token * * Access tokens are short-lived (typically 15 minutes) and used for API authentication. * They contain user identity and authorization information. * * @param data - Token payload data * @returns Signed JWT access token */ generateAccessToken(data: { userId: string; email: string; sessionId: string; tokenFamily: string; }): Promise; /** * Generate a refresh token * * Refresh tokens are long-lived (typically 30 days) and used to obtain new access tokens. * They should be stored securely and rotated on each use. * * NOTE: Refresh tokens always use a symmetric algorithm (HS256/HS384/HS512) * because RefreshTokenConfig only provides a secret, not a privateKey. * This ensures compatibility between the algorithm and key type. * * @param data - Token payload data * @returns Signed JWT refresh token */ generateRefreshToken(data: { userId: string; email: string; sessionId: string; tokenFamily: string; /** * Optional per-request override for this token's expiresIn. * Falls back to config.refreshToken.expiresIn when unset. */ expiresIn?: string | number; }): Promise; /** * Validate an access token * * Verifies: * - Token signature is valid * - Token hasn't expired * - Token type is 'access' * - Token structure is correct * * @param token - JWT access token to validate * @returns Validation result with payload or error * * @example * ```typescript * const result = await jwtService.validateAccessToken(token); * * if (!result.valid) { * if (result.errorType === 'expired') { * // Attempt to refresh token * } else { * // Invalid token, reject request * } * } * ``` */ validateAccessToken(token: string): Promise; /** * Validate a refresh token * * Similar to access token validation but checks for 'refresh' type. * Also verifies token hasn't been used before (if rotation is enabled). * * @param token - JWT refresh token to validate * @returns Validation result with payload or error */ validateRefreshToken(token: string): Promise; /** * Decode a token without verification * * WARNING: This method does NOT validate the token signature or expiration. * Only use for non-security-critical operations like logging or analytics. * * @param token - JWT token to decode * @returns Decoded payload or null if malformed */ decodeToken(token: string): JwtPayload | null; /** * Convert base64url-encoded strings to standard base64 for decoding. * @private */ private base64UrlToBase64; /** * Generate a unique token family identifier * * Token families are used to track token rotation and detect reuse attacks. * All tokens in the same "family" (original + rotated versions) share this ID. * * SECURITY FIX #10: Increased from 16 bytes (128 bits) to 32 bytes (256 bits) * * @returns Random token family ID (256 bits) */ generateTokenFamily(): string; /** * Hash a token for storage * * Tokens should be hashed before storing in the database for security. * This prevents token exposure if the database is compromised. * * @param token - Token to hash * @returns SHA-256 hash of the token */ hashToken(token: string): string; /** * Get access token expiry time in seconds * * @returns Access token expiry time in seconds * * @example * ```typescript * const expiry = jwtService.getAccessTokenExpiry(); * console.log(expiry); // 900 (15 minutes) * ``` */ getAccessTokenExpiry(): number; /** * Get refresh token TTL in seconds * * Used for setting expiration on used-token tracking in storage. * * @param override - Optional per-request TTL (duration string or seconds) * that overrides the configured refreshToken.expiresIn. * Used by hybrid-policy resolution so storage TTLs match * the actual issued token's lifetime. * @returns TTL in seconds */ getRefreshTokenTTL(override?: string | number): number; /** * Extract token from Authorization header * * Supports standard "Bearer " format * * @param authHeader - Authorization header value * @returns Extracted token or null * * @example * ```typescript * const token = jwtService.extractTokenFromHeader('Bearer eyJhbGc...'); * // Returns: 'eyJhbGc...' * ``` */ extractTokenFromHeader(authHeader?: string): string | null; /** * Parse expiration time from string or number * @param expiresIn - Expiration time (e.g., '15m', 900, '1h') * @returns Expiration time in seconds */ private parseExpiresIn; /** * Handle JWT validation errors and convert to standardized result * @param error - Error from JWT verification * @returns Standardized validation result */ private handleValidationError; } //# sourceMappingURL=jwt.service.d.ts.map