2.7 KiB
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 parsedJoseHeader, including unknown parameters. undefined/nullreturn → typedJWS.KEY_NOT_FOUNDthrown by the pipeline.
Usage: Basic Example
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
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> | 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.