3.8 KiB
@suaveplan/api-auth-jwt — JWT session resolver
Package: @suaveplan/api-auth-jwt
Status: PROPOSED
Type: new-package
Stage: 4 (Base adapters) — see umbrella openspec/changes/api-plugin-foundation/
Why
Per design doc §2.3, auth is split into two distinct contracts: session resolution (Request → Principal | null) and account lifecycle (provider). api-auth-jwt implements only the session-resolver half — it verifies a JWT from an Authorization: Bearer header or cookie against a JWKS URL or static key using @suaveplan/jws, and projects the resulting claims onto a Principal. It is NOT a provider: no login/register/refresh routes. This is what you pair with hosted IdPs (Clerk, Auth0, Cognito) or with any self-hosted JWT-issuing provider.
Per Stage 4: ships both a direct callable surface and a worker-proxy surface (§5.2a). runAuthSessionConformance passes against both.
What changes
Net-new package depending on @suaveplan/api-auth (contract), @suaveplan/api-types, @suaveplan/api-worker-rpc, @suaveplan/jws, @suaveplan/error, and zod.
- ADDED:
createJwtSessionResolver(opts)— direct-surface factory returning anAuthAdapter(session-resolver only). - ADDED:
createJwtSessionResolverWorkerProxy(port, opts)— proxy-surface factory. - ADDED:
JwtSessionResolverOptions— Zod schema (jwksUrl?: string,staticKey?: JsonWebKey,algorithms: string[],audience?: string | string[],issuer?: string | string[],clockToleranceSec?: number,cookieName?: string,headerName?: string,claimToPrincipal?: (claims) => Principal). - ADDED:
defaultClaimToPrincipal— projectssub/scope/scopes/tenant_id/tidonto aPrincipal. - ADDED:
securitySchemeContribution()— returns OpenAPIsecuritySchemesblock describing the Bearer JWT scheme (consumed byapi-openapiStage 8). - ADDED:
AUTH_JWT_*error codes (AUTH_JWT_MISSING_TOKEN,AUTH_JWT_INVALID_SIGNATURE,AUTH_JWT_EXPIRED,AUTH_JWT_WRONG_AUDIENCE,AUTH_JWT_WRONG_ISSUER,AUTH_JWT_ALGORITHM_NOT_ALLOWED,AUTH_JWT_JWKS_FETCH_FAILED,AUTH_JWT_INVALID_OPTIONS). - ADDED: JWKS caching with TTL + ETag revalidation; refresh on
kidmiss.
Out of scope
- Account lifecycle (register/login/logout/refresh) — that's
auth.provider, not this package. Seeapi-auth-better-auth(Stage 6) andapi-auth-rolled(Stage 8). - Cookie-based CSRF gating — the host's request pipeline (§6) triggers
Bun.CSRFbased on auth-adapter declaration; this package only declares whether cookie auth is in use. - Multi-resolver chaining — handled by
api-authcore (Stage 1).
Source mapping
Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.
| Requirement | Design doc section |
|---|---|
| Session resolver, not provider | §2.3 |
| JWKS + static-key support | §2.3 (api-auth-jwt bullet) |
| Worker-proxy variant | §5.2a |
| Conformance both surfaces | §13.4 |
| OpenAPI securityScheme contribution | §7 |
Verification approach
src/factory/create-jwt-session-resolver.test.ts— direct-surface tests against real@suaveplan/jws-signed tokens.src/factory/create-jwt-session-resolver.spec-conformance.test.ts—runAuthSessionConformance("jwt-direct", ...).src/proxy/create-jwt-session-resolver-worker-proxy.test.ts— proxy unit tests overMessageChannel.src/proxy/create-jwt-session-resolver-worker-proxy.spec-conformance.test.ts—runAuthSessionConformance("jwt-worker-proxy", ...).src/jwks/jwks-cache.test.ts— TTL, ETag,kidmiss refresh.src/claim-mapping/default-claim-to-principal.test.ts— claim projection, scope normalization, tenant extraction.tests/integration/integration.test.ts— sign with@suaveplan/jws, verify through this resolver end-to-end.
Migration impact
None — additive only.