# `KeyResolver` ## Purpose Caller-supplied key-resolution function consulted when `header.kid` is present and no key has been supplied via `opts.key`, `header.jwk`, or `header.x5c`. The resolver receives the parsed header so dispatch can branch on additional parameters (`iss`, custom tenant headers, environment-specific kid namespaces). Returning `undefined` surfaces as `JwsError(JWS.KEY_NOT_FOUND)`; throwing propagates verbatim so callers can map their own error taxonomy onto JWS verification. ## Features - Async or sync — both signatures are accepted. - Receives `(kid, header)`; the header is the parsed `JoseHeader`, including unknown parameters. - `undefined` / `null` return → typed `JWS.KEY_NOT_FOUND` thrown by the pipeline. ## Usage: Basic Example ```ts import { compactVerify, type KeyResolver } from "@suaveplan/jws"; const keyResolver: KeyResolver = async (kid) => keyStore.get(kid); await compactVerify(token, undefined, { algorithms: ["RS256"], keyResolver, }); ``` ## Usage: Advanced Example ```ts import { compactVerify, JwsError, type KeyResolver } from "@suaveplan/jws"; // Dispatch on a custom tenant header: const keyResolver: KeyResolver = async (kid, header) => { const tenant = header["tenant"] as string | undefined; if (tenant === "acme") return acmeKeyStore.get(kid); if (tenant === "globex") return globexKeyStore.get(kid); return undefined; // → KEY_NOT_FOUND }; try { await compactVerify(token, undefined, { algorithms: ["RS256"], keyResolver, }); } catch (err) { if (err instanceof JwsError && err.code === "JWS.KEY_NOT_FOUND") { // Inspect err.context.kid for the unresolved id. } } ``` ## API Reference - `type KeyResolver = (kid: string, header: JoseHeader) => Promise | unknown` Returning anything other than `undefined` / `null` is treated as the verification key and passed to the algorithm impl unchanged. The pipeline performs no type-check on the returned value — it relies on the algorithm impl to throw `JWS.KEY_ALG_MISMATCH` if the resolved key doesn't match the declared `alg`. ## Implementation Notes Resolution priority in the verify pipeline is `opts.key → header.jwk → header.x5c → opts.keyResolver(header.kid)`. The resolver only runs when none of the earlier sources resolved a key; this means a token carrying both `kid` AND `jwk` will route through `jwk` first (assuming `allowEmbeddedJwk: true`). That ordering is deliberate: when a producer self-describes a key in the token, the consumer's stored key for the same `kid` should be re-verified against the embedded one — but doing that match is the consumer's responsibility, not this library's. The pipeline simply uses whichever source the caller has opted in to first.