Files
suaveplan/packages/api-stack/api/api-auth-jwt/README.md
2026-05-27 23:29:53 +01:00

6.4 KiB

@suaveplan/api-auth-jwt — concrete JWT session resolver for @suaveplan/api-auth. Verifies a JWT presented on the Authorization header (or a configurable cookie) using @suaveplan/jws against either a JWKS endpoint (TTL + negative-cache + in-flight dedup) or a single static JsonWebKey; projects validated claims onto a Principal via an overridable mapper. Returns null on parse/verify failure (chain semantics) and contributes a Bearer JWT OpenAPI security scheme.

Stage-4 concrete SessionResolver for the @suaveplan/api plugin system. Verifies a JWT presented on the Authorization: Bearer <jwt> header (or a configured cookie) against either a remote JWKS endpoint or a single static JWK, then projects the validated claims onto the foundation's Principal contract via an overridable mapper.

This is the adapter you pair with hosted identity providers (Clerk, Auth0, Cognito, Stytch, custom Keycloak deployments) or with any self-hosted JWT-issuing provider. It is session-resolver only — login / register / refresh routes are the responsibility of an auth provider, not this package.

Documentation tree

  • claims-mapper — Default and runtime helpers for converting a verified JWT payload (Claims) into a Principal from `@suaveplan/api-ty…
    • claims-mapper — Default and runtime helpers for converting a verified JWT payload (Claims) into a Principal from `@suaveplan/api-ty…
  • error-codes — Stable error-code registry for @suaveplan/api-auth-jwt, exposing the AUTH_JWT.* namespace via defineCodes from `@…
    • error-codes — Stable error-code registry for @suaveplan/api-auth-jwt, exposing the AUTH_JWT.* namespace via defineCodes from `@…
  • jwks-cache — In-process JWKS cache for @suaveplan/api-auth-jwt. Fetches a JWKS document from a remote endpoint, holds it under a p…
    • jwks-cache — In-process JWKS cache for @suaveplan/api-auth-jwt. Fetches a JWKS document from a remote endpoint, holds it under a p…
  • options — Defines the Zod-validated boundary for createJwtSessionResolver and the companion validateOptions helper. The bound…
    • options — Defines the Zod-validated boundary for createJwtSessionResolver and the companion validateOptions helper. The bound…
  • resolver — Stage-4 concrete SessionResolver factory for the API plugin system. Reads a JWT from the request (Authorization h…
    • resolver — Stage-4 concrete SessionResolver factory for the API plugin system. Reads a JWT from the request (Authorization h…
  • security-scheme — Builds the SecuritySchemeContribution this adapter contributes to the generated OpenAPI document. The shape is the ca…
    • security-scheme — Builds the SecuritySchemeContribution this adapter contributes to the generated OpenAPI document. The shape is the ca…

Surface

  • ./resolver — createJwtSessionResolver(opts).
  • ./jwks-cache — JwksCache with TTL, negative cache, in-flight dedup, kid-miss refresh.
  • ./claims-mapper — defaultClaimsToPrincipal + runClaimsMapper helpers.
  • ./security-scheme — JWT_BEARER_SCHEME and createJwtBearerSecurityScheme(name) for OpenAPI emission.
  • ./error-codes — AUTH_JWT_* codes
    • AuthJwtError class.
  • ./options — Zod-validated options boundary.

Quick start

import { createJwtSessionResolver } from "@suaveplan/api-auth-jwt/resolver";
import { JWT_BEARER_SCHEME } from "@suaveplan/api-auth-jwt/security-scheme";

const resolver = createJwtSessionResolver({
    jwksUrl: "https://idp.example/.well-known/jwks.json",
    issuer: "https://idp.example",
    audience: "https://api.example",
    algorithms: ["RS256"],
});

// Use in an api-host AuthAdapter:
const adapter = {
    resolver,
    securityScheme: JWT_BEARER_SCHEME,
};

Pairing with hosted IdPs

The package is designed to be paired with an external identity provider that already issues JWTs. The recipe is the same for every provider — plug in the provider's JWKS URL, issuer, and audience, then add a custom claimsToPrincipal when the provider uses non-standard claim names.

Clerk

createJwtSessionResolver({
    jwksUrl: "https://your-instance.clerk.accounts.dev/.well-known/jwks.json",
    issuer: "https://your-instance.clerk.accounts.dev",
    audience: "your-frontend-api",
    algorithms: ["RS256"],
    claimsToPrincipal: (claims) => {
        const sub = claims["sub"];
        if (typeof sub !== "string") return null;
        return {
            id: sub as never,
            scopes: [] as never[],
            tenantId: typeof claims["org_id"] === "string"
                ? (claims["org_id"] as never)
                : undefined,
            claims,
        };
    },
});

Auth0

createJwtSessionResolver({
    jwksUrl: "https://your-tenant.auth0.com/.well-known/jwks.json",
    issuer: "https://your-tenant.auth0.com/",
    audience: "https://api.example.com",
    algorithms: ["RS256"],
});

Cognito

createJwtSessionResolver({
    jwksUrl: "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXX/.well-known/jwks.json",
    issuer: "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXX",
    audience: "your-cognito-app-client-id",
    algorithms: ["RS256"],
});

Semantics

resolve() returns null on any per-request soft failure (missing token, malformed token, bad signature, wrong issuer, wrong audience, expired, not-yet-valid, no matching JWKS key). That collapse lets the upstream SessionResolverChain fall through to the next adapter (anonymous, static-bearer, etc.). It throws only on infrastructure failures — AUTH_JWT.JWKS_FETCH_FAILED when the JWKS endpoint is unreachable, AUTH_JWT.INVALID_KEY_FORMAT for a malformed static key, AUTH_JWT.INVALID_CLAIMS_MAPPER when the caller's mapper throws or returns a non-Principal value.

License

MIT — see the repository.