//#region src/jwt.d.ts /** * @file lib/jwt.ts * * # JWT Verification * * Stateless JWT verification via JWKS, with in-process key caching. * * ## Anatomy of a JWT * * A compact JWT (JWS compact serialisation) is three Base64URL-encoded parts * joined by dots: * * ``` *
.. * ``` * * - **Header** — metadata about the token: signing algorithm, key ID, token type. * - **Payload** — the claims: user identity, expiry, issuer, audience, etc. * - **Signature** — `SIGN(privateKey, "
.")`. Any party with the * matching public key can verify it without contacting the auth server. * * A valid signature proves two things: * * 1. **Authenticity** — the token was produced by the holder of the private key. * 2. **Integrity** — neither the header nor the payload has been altered since signing. * * Signature verification alone is not sufficient. The claims inside the payload * must also be checked (expiry, issuer, audience, …). Both steps are performed * by {@link verifyJwt}. * * --- * * ## JWT Header fields * * The header is a JSON object Base64URL-encoded into the first segment. * * | Field | Purpose | Verified | * |-------|---------|---------| * | `alg` | Signing algorithm (`RS256`, `ES256`, …) | Checked against {@link VerifyJwtOptions.allowedAlgorithms} before JWKS is fetched | * | `kid` | Key ID — selects the matching public key from the JWKS | Used for key lookup; falls back to the first JWKS key when absent | * | `typ` | Token type (`JWT`, `at+JWT`, …) | Checked against {@link VerifyJwtOptions.allowedTokenTypes} (case-insensitive) | * | ~~`enc`~~ | ~~Encryption algorithm — present in JWE (encrypted) tokens only~~ | ~~Not applicable — we verify JWS (signed) tokens, not JWE (encrypted)~~ | * | ~~`cty`~~ | ~~Content type — describes the secured content media type~~ | ~~Not applicable — not set by standard OIDC/OAuth auth servers~~ | * * --- * * ## JWT Payload claims * * The payload is a JSON object Base64URL-encoded into the second segment. * * ### Validated claims * * | Claim | Purpose | How | * |-------|---------|-----| * | `iss` | Issuer — the URL of the auth server that issued the token | Must be present and must equal {@link VerifyJwtOptions.issuerUrl} | * | `exp` | Expiration time — Unix timestamp after which the token is invalid | Must be present and in the future, allowing {@link VerifyJwtOptions.clockSkewMs} tolerance | * | `nbf` | Not before — Unix timestamp before which the token must not be accepted | Must be in the past, allowing {@link VerifyJwtOptions.clockSkewMs} tolerance | * | `iat` | Issued at — Unix timestamp when the token was created | Must not be in the future beyond {@link VerifyJwtOptions.clockSkewMs} | * | `aud` | Audience — the intended recipient(s) of the token | Checked against {@link VerifyJwtOptions.audience} when that option is provided | * * ### Read-only claims (extracted into the session, not further validated) * * | Claim | Purpose | * |-------|---------| * | `sub` | Subject — the user's unique identifier | * | `email` | User's email address | * | `name` | User's display name | * * ### Not validated * * | Claim | Purpose | Why skipped | * |-------|---------|-------------| * | ~~`jti`~~ | ~~JWT ID — a unique identifier for this specific token instance~~ | ~~Revocation requires a server-side blocklist; not feasible in stateless edge middleware~~ | * | ~~`azp`~~ | ~~Authorized party — the OAuth 2.0 client that requested the token~~ | ~~The proxy architecture means the browser never contacts the auth server directly, making cross-origin token replay a non-threat~~ | * | ~~`nonce`~~ | ~~OIDC nonce — binds a token to a specific authorisation request~~ | ~~Not applicable in our password/cookie flow (no OIDC redirect round-trip)~~ | * | ~~`acr`~~ | ~~Authentication class reference — describes the strength of the authentication~~ | ~~Not validated (future: require MFA for sensitive routes)~~ | * | ~~`amr`~~ | ~~Authentication methods reference — list of methods used to authenticate~~ | ~~Not validated~~ | * | ~~`scope`~~ | ~~OAuth 2.0 scopes granted to the token~~ | ~~Not validated (future: per-route scope enforcement)~~ | * * --- * * ## JWKS caching * * Each issuer's whole JWKS document is cached in-process, keyed by its JWKS URI, * for {@link JWKS_TTL_MS} milliseconds (default 5 minutes); keys are imported * once and reused. Keying by the URI keeps two issuers that publish the same * `kid` apart. * * The document is fetched from `{issuerUrl}/auth/keys` on first use and after * the TTL. A token naming a `kid` the cached document lacks refreshes it — how * a rotated key is picked up — but no sooner than {@link JWKS_MIN_REFRESH_MS} * after the last fetch. The `kid` header is attacker-controlled, so a per-`kid` * cache would let any request with a made-up `kid` cost a JWKS fetch. * * > **Note (serverless):** the cache is module-level. In serverless environments * > each cold start gets a fresh cache; warm instances share it across requests. * * --- * * ## Runtime note * * This file is the **shared implementation** used by both `sdk-next` (Edge * runtime) and `sdk-nuxt` (Node.js/Nitro). `base64UrlDecode` uses `atob`, * which is available in both environments (Edge natively; Node.js since v16). * The previous per-SDK copies (one using `atob`, one using `Buffer`) have * been consolidated here. */ /** How long a cached JWKS document is considered fresh before re-fetching. */ export declare const JWKS_TTL_MS: number; /** * The least time between two JWKS fetches for one issuer. A token naming a * `kid` the cached document lacks triggers a refresh — that is how a rotated * key is picked up — but at most this often, so a stream of tokens with random * `kid` values cannot turn every request into a JWKS fetch. */ export declare const JWKS_MIN_REFRESH_MS: number; /** * The subset of standard JWT payload claims this module reads or validates. * * All fields are `readonly`. Additional custom claims are preserved under * the index signature and accessible via `payload["my-claim"]`. * * Refer to the module-level documentation for the full verification matrix. */ export interface JwtPayload { /** Issuer (`iss`) — identifies the auth server that created the token. */ readonly iss?: string; /** Subject (`sub`) — the user's unique identifier. */ readonly sub?: string; /** * Audience (`aud`) — the intended recipient(s). * A single string or an array of strings per RFC 7519 §4.1.3. */ readonly aud?: string | readonly string[]; /** Expiration time (`exp`) — seconds since Unix epoch. */ readonly exp?: number; /** Not before (`nbf`) — seconds since Unix epoch. */ readonly nbf?: number; /** Issued at (`iat`) — seconds since Unix epoch. */ readonly iat?: number; /** User's email address (custom claim). */ readonly email?: string; /** User's display name (custom claim). */ readonly name?: string; readonly [key: string]: unknown; } /** * The typed representation of a JWT header. * * The three fields below are the only ones this library reads. Additional * header parameters are preserved under the index signature. */ export interface JwtHeader { /** Signing algorithm (`alg`) — e.g. `"RS256"`, `"ES256"`. */ readonly alg?: string; /** Token type (`typ`) — e.g. `"JWT"`, `"at+JWT"`. */ readonly typ?: string; /** Key ID (`kid`) — selects the matching public key from the JWKS. */ readonly kid?: string; readonly [key: string]: unknown; } /** * The decoded, unverified parts of a compact-serialised JWT. * * **Do not act on these values before signature verification.** * This type is returned by {@link decodeJwt} purely to carry the * raw data into the next step. */ export interface DecodedJwt { readonly header: JwtHeader; readonly payload: JwtPayload; } /** * Options for {@link verifyJwt}. * * All fields are `readonly` — this object is never mutated after construction. */ export interface VerifyJwtOptions { /** * Base URL of the auth backend. * * Serves two purposes: * 1. Determines the JWKS endpoint: `{issuerUrl}/auth/keys` * 2. Is compared against the `iss` claim when present in the token. */ readonly issuerUrl: string; /** * Restrict accepted `alg` header values. Tokens whose algorithm is not in * this list are rejected before the JWKS is fetched. * When `undefined`, all algorithms are accepted (the middleware layer * defaults to `["RS256", "ES256"]` before passing options here). */ readonly allowedAlgorithms?: readonly string[]; /** * Clock-skew tolerance in milliseconds, applied symmetrically to `exp`, * `nbf`, and `iat` checks to accommodate minor differences between clocks. */ readonly clockSkewMs: number; /** * Expected `aud` claim value(s). When `undefined`, audience is not * validated. Provide a string or array when the auth server sets `aud` * and you want to ensure the token was issued specifically for your app. */ readonly audience?: string | readonly string[]; /** * Accepted `typ` header values (case-insensitive). * * Guards against using a refresh token or ID token where an access token * is expected. Set to an empty array (`[]`) to disable this check. * * @default ["JWT", "at+JWT"] */ readonly allowedTokenTypes: readonly string[]; /** * Timeout in milliseconds for JWKS endpoint requests. * Requests that do not complete within this window are aborted and the * token is rejected (function returns `null`). * @default 5000 */ readonly jwksTimeoutMs?: number; } /** * Forgets every cached JWKS document, so the next verification fetches the key * set anew. Use it to drop a compromised key at once instead of waiting out * {@link JWKS_TTL_MS}, and in tests that publish different keys per case. */ export declare function clearJwksCache(): void; /** * Decodes a Base64URL-encoded string into a `Uint8Array`. * * The Edge runtime has no `Buffer`, so this uses `atob` after translating the * Base64URL alphabet (`-` → `+`, `_` → `/`) and padding to a multiple of four. * * @param input - A Base64URL-encoded string (padding is optional). * @returns The decoded bytes. */ export declare function base64UrlDecode(input: string): Uint8Array; /** * Decodes a compact-serialised JWT into its header and payload **without** * verifying the signature. * * **Do not trust the result of this function alone.** Use it only as the first * step of a full verification pipeline — the signature check in {@link verifyJwt} * must follow before any claim can be trusted. * * @param token - A compact JWT string (`header.payload.signature`). * @returns The decoded {@link DecodedJwt}. * @throws {TypeError} When `token` has fewer than three dot-separated segments. */ export declare function decodeJwt(token: string): DecodedJwt; /** * Returns `true` when `token` looks structurally like a signed JWT (JWS) — * NOT an encrypted token (JWE). * * JWS compact: 3 dot-separated segments; header has `alg` but NOT `enc`. * JWE compact: 5 dot-separated segments; header has BOTH `alg` and `enc`. * * The Zitadel backend issues JWE tokens (AES-256-GCM via go-jose * `A256GCMKW/A256GCM`), whose compact form is * `header.encrypted_key.iv.ciphertext.tag`. The header decodes to JSON with * `"alg":"A256GCMKW","enc":"A256GCM"`. Checking for the absence of `enc` * correctly identifies signed JWTs without false-positives on JWE tokens. * * This is a structural check only — not a security check. Use it to route a * token to the correct validation path ({@link verifyJwt} for JWS, a backend * session lookup for everything else), never to trust one. */ export declare function isJwtShaped(token: string): boolean; /** * Verifies a compact-serialised JWT and returns its validated payload. * * ## Verification steps (in order) * * 1. Split the token — returns `null` immediately when fewer than three * dot-separated segments are present. * 2. Reject if `alg` is not in {@link VerifyJwtOptions.allowedAlgorithms} (when set). * 3. Reject if `typ` is not in {@link VerifyJwtOptions.allowedTokenTypes} (when non-empty). * 4. Fetch (or retrieve from cache) the matching public key from the JWKS endpoint. * 5. Verify the cryptographic signature over `header.payload`. * 6. Reject if `iss` is absent or does not equal {@link VerifyJwtOptions.issuerUrl}. * 7. Reject if {@link VerifyJwtOptions.audience} is set and `aud` does not contain it. * 8. Reject if `exp` is absent or is in the past (minus clock-skew tolerance). * 9. Reject if `nbf` is in the future (plus clock-skew tolerance). * 10. Reject if `iat` is in the future (plus clock-skew tolerance). * * Returns `null` on any failure rather than throwing, so callers can treat a * bad token identically to a missing one without try/catch at the call site. * * @param token - The raw compact-serialised JWT string. * @param opts - Verification options; see {@link VerifyJwtOptions}. * @returns The verified {@link JwtPayload}, or `null` if verification fails. */ export declare function verifyJwt(token: string, opts: VerifyJwtOptions): Promise; //#endregion //# sourceMappingURL=jwt.d.mts.map