import { OnModuleInit } from '@nestjs/common'; import type { TKeycloakModuleOptions, IKeycloakTokenClaims, IKeycloakUser } from '../keycloak.types.js'; /** * Result returned by `KeycloakTokenValidationService.ValidateToken`. * * On success, `valid` is `true` and `claims` contains the decoded token payload. * On failure, `valid` is `false` and `error` contains a short error code string * (e.g. `'token_expired'`, `'invalid_audience'`, `'token_inactive'`). * * @example * ```ts * const result = await validationService.ValidateToken(token); * if (result.valid && result.claims) { * console.log(result.claims.sub); // User ID * console.log(result.claims.realm_access?.roles); // Realm roles * } else { * console.error('Validation failed:', result.error); * } * ``` */ export interface ITokenValidationResult { /** Whether the token passed all validation checks */ valid: boolean; /** Decoded token claims — present only when `valid` is `true` */ claims?: IKeycloakTokenClaims; /** Short error code describing why validation failed — present only when `valid` is `false` */ error?: string; } /** * Keycloak Token Validation Service * * Validates JWT tokens issued by Keycloak in two modes: * - **Online mode (default)**: Calls Keycloak's token introspection endpoint to validate the token * (requires real-time network access to Keycloak) * - **Offline mode**: Validates JWTs locally using JWKS (no network call; suitable for high-traffic scenarios) * * After successful validation, extracts user identity and roles from token claims. * * @example * ```ts * // Online mode validation * const result = await this.validationService.ValidateToken(token); * if (result.valid) { * const user = this.validationService.ExtractUser(result.claims!); * } * * // Offline mode uses JWKS-based verification (faster, no network call) * ``` */ export declare class KeycloakTokenValidationService implements OnModuleInit { private readonly Logger; private readonly Options; private JwksSet; /** Supported JWT algorithms for offline mode validation */ private readonly SupportedAlgorithms; /** Positive cache for valid introspection results, keyed by token hash */ private readonly IntrospectionCache; /** Negative cache for failed introspection results, keyed by token hash (only used when failedIntrospectionCacheTtlMs is configured) */ private readonly FailedIntrospectionCache; /** Number of `ValidateTokenOnline` calls seen so far; drives the opportunistic cache sweep. */ private CacheAccessCount; constructor(options: TKeycloakModuleOptions); /** * NestJS lifecycle hook — initializes JWKS set for offline mode validation. * * In offline mode, creates a remote JWKS set from the Keycloak JWKS endpoint. * The JWKS set is cached according to jwksCacheTtlMs configuration. * In online mode, this hook does nothing (introspection endpoint is used instead). * * @throws {Error} If JWKS initialization fails in offline mode */ onModuleInit(): Promise; /** * Validate a JWT token issued by Keycloak. * * Routes to the appropriate validation mode based on configuration: * - **Online**: Calls the Keycloak introspection endpoint (requires network access) * - **Offline**: Verifies JWT signature using cached JWKS (no network call) * * Both modes verify token expiration and audience/issuer claims. * * @param token - The JWT to validate (Bearer token without "Bearer " prefix) * @returns ITokenValidationResult — `{ valid: true, claims }` on success, or `{ valid: false, error }` on failure (error codes: 'token_expired', 'invalid_issuer', 'invalid_audience', etc.) * * @example * ```ts * const result = await this.ValidateToken(jwtToken); * if (result.valid && result.claims) { * const user = this.ExtractUser(result.claims); * } * ``` */ ValidateToken(token: string): Promise; private ValidateTokenOnline; /** * Hash a token for use as a cache key. * Uses SHA-256 to produce a fixed-size key from potentially long tokens. */ private HashToken; /** * Removes expired entries from both introspection caches. * * Runs opportunistically — every `CACHE_SWEEP_INTERVAL_ACCESSES` calls to `ValidateTokenOnline` — * so entries for tokens that are validated once and never looked up again are eventually * reclaimed, instead of only being evicted lazily when the same cache key is looked up again * after expiry. */ private SweepExpiredCacheEntries; /** * Inserts an entry into a bounded introspection cache, evicting the oldest entry (by insertion * order, per `Map` iteration semantics) first if the cache is already at `MAX_INTROSPECTION_CACHE_ENTRIES`. * Bounds worst-case memory growth under high token cardinality independently of TTL expiry. * * @param cache - The cache map to insert into * @param key - The cache key (SHA-256 token hash) * @param value - The cache entry to store */ private SetBoundedCacheEntry; private ValidateTokenOffline; /** * Extract user identity and roles from validated token claims. * * Maps Keycloak token claims to a simplified `IKeycloakUser` object. * Extracts both realm-level roles (`realm_access.roles`) and client-specific roles * (`resource_access[clientId].roles`). * * If a `mapUserFn` hook is configured, applies it to the default-extracted user object, * allowing callers to reshape or enrich the identity for non-standard Keycloak mappers * (custom claims, multi-tenant namespacing, etc). The hook's return value becomes the * final user object. * * @param claims - The validated Keycloak token claims * @returns IKeycloakUser object with ID, email, username, name, and both realm and client roles * (or the result of mapUserFn if configured) * @throws Propagates whatever `mapUserFn` throws, if configured — user-mapping failures are not swallowed * * @example * ```ts * const user = this.ExtractUser(claims); * // { id: 'user-uuid', email: 'user@example.com', username: 'john_doe', * // name: 'John Doe', realmRoles: ['admin'], clientRoles: ['read'] } * ``` */ ExtractUser(claims: IKeycloakTokenClaims): IKeycloakUser; private Log; } //# sourceMappingURL=keycloak-token-validation.service.d.ts.map