Files
skills/docs/api-stack/api/api-auth-bearer-static/README.md
2026-05-29 08:20:56 +01:00

156 lines
8.3 KiB
Markdown

# @suaveplan/api-auth-bearer-static — SessionResolver adapter for service-to-service static bearer tokens. Matches Authorization: Bearer <token> against a fixed token→Principal table using constant-time comparison from @suaveplan/crypto-utils; unknown tokens defer to the next chain entry. Supports an optional per-token mutual-TLS gate that rejects when the host did not surface request.tls.peerCertificate. Ships both a direct surface (createBearerStaticResolver) and a worker-proxy surface (createBearerStaticResolverWorkerProxy) over @suaveplan/api-worker-rpc; both pass runAuthSessionConformance from @suaveplan/api-auth/testing. OpenAPI security-scheme contribution (HTTP Bearer). Zod-validated options, AUTH_BEARER_STATIC.* error codes, OTel match counter partitioned by outcome.
Stage-4 service-to-service `SessionResolver` adapter for the
`@suaveplan/api` plugin system. Matches an inbound `Authorization:
Bearer <token>` header against a fixed `token → Principal` table using
constant-time comparison from `@suaveplan/crypto-utils`. Unknown tokens
return `null` so the host's `SessionResolverChain` falls through to the
next adapter (typically `api-auth-jwt` for user sessions).
Ships both the direct surface (`createBearerStaticResolver`) and the
worker-proxy surface (`createBearerStaticResolverWorkerProxy` +
`serveBearerStaticResolverOnPort`) over `@suaveplan/api-worker-rpc`; both
pass `runAuthSessionConformance` from `@suaveplan/api-auth/testing`.
<!-- begin:auto-docs -->
## Documentation tree
- [constant-time](./src/constant-time/token-match.md) — `findTokenIndex` performs a constant-time lookup of a presented bearer token against a fixed table, returning the match…
- [token-match](./src/constant-time/token-match.md) — `findTokenIndex` performs a constant-time lookup of a presented bearer token against a fixed table, returning the match…
- [error-codes](./src/error-codes/error-codes.md) — The error-codes module defines the stable `AUTH_BEARER_STATIC.*` namespace that every public failure path in this packa…
- [error-codes](./src/error-codes/error-codes.md) — The error-codes module defines the stable `AUTH_BEARER_STATIC.*` namespace that every public failure path in this packa…
- [factory](./src/factory/create-bearer-static-resolver.md) — `createBearerStaticResolver` is the direct-surface entry point of the package. It accepts validated `BearerStaticResolv…
- [create-bearer-static-resolver](./src/factory/create-bearer-static-resolver.md) — `createBearerStaticResolver` is the direct-surface entry point of the package. It accepts validated `BearerStaticResolv…
- [mtls](./src/mtls/mtls-gate.md) — The mTLS gate enforces an optional second factor on individual token table entries. When operators mark an entry with `…
- [mtls-gate](./src/mtls/mtls-gate.md) — The mTLS gate enforces an optional second factor on individual token table entries. When operators mark an entry with `…
- [options](./src/options/bearer-static-resolver-options.md) — The options module owns the public construction surface of every resolver in this package. It defines the `BearerStatic…
- [bearer-static-resolver-options](./src/options/bearer-static-resolver-options.md) — The options module owns the public construction surface of every resolver in this package. It defines the `BearerStatic…
- [proxy](./src/proxy/create-bearer-static-resolver-worker-proxy.md) — The worker-proxy surface is the second of the two Stage-4 surfaces this adapter ships (`§5.2a`). It lets the host insta…
- [create-bearer-static-resolver-worker-proxy](./src/proxy/create-bearer-static-resolver-worker-proxy.md) — The worker-proxy surface is the second of the two Stage-4 surfaces this adapter ships (`§5.2a`). It lets the host insta…
- [security-scheme](./src/security-scheme/security-scheme-contribution.md) — This module builds the OpenAPI 3.1 `components.securitySchemes` contribution the adapter publishes to the host's `AuthA…
- [security-scheme-contribution](./src/security-scheme/security-scheme-contribution.md) — This module builds the OpenAPI 3.1 `components.securitySchemes` contribution the adapter publishes to the host's `AuthA…
- [telemetry](./src/telemetry/instruments.md) — The telemetry module owns the single OpenTelemetry counter this package emits — `api.auth.bearer_static_match_total` —…
- [instruments](./src/telemetry/instruments.md) — The telemetry module owns the single OpenTelemetry counter this package emits — `api.auth.bearer_static_match_total` —…
<!-- end:auto-docs -->
## Install
```sh
bun add @suaveplan/api-auth-bearer-static
```
## Direct surface
```ts
import { createBearerStaticResolver } from "@suaveplan/api-auth-bearer-static";
import { SessionResolverChain } from "@suaveplan/api-auth";
const serviceAuth = createBearerStaticResolver({
tokens: [
{
token: process.env.CRON_TOKEN,
principal: {
id: "svc_cron",
scopes: ["jobs:run"],
claims: { kind: "service" },
},
},
{
token: process.env.ADMIN_TOKEN,
principal: {
id: "svc_admin",
scopes: ["admin:*"],
claims: { kind: "admin" },
},
mtlsRequired: true,
},
],
});
// Chained — service tokens first, JWT users fall through.
const chain = new SessionResolverChain([serviceAuth, jwtResolver]);
```
## Chained-resolver pattern with `api-auth-jwt`
The intended deployment topology pairs this adapter with
`@suaveplan/api-auth-jwt` for user sessions. Static-token callers match
first; everything else flows to JWT. The chain's `first-match-wins`
semantics means unknown tokens never throw — they simply move on.
```ts
import { SessionResolverChain } from "@suaveplan/api-auth";
import { createBearerStaticResolver } from "@suaveplan/api-auth-bearer-static";
import { createJwtSessionResolver } from "@suaveplan/api-auth-jwt"; // future sibling
const chain = new SessionResolverChain([
createBearerStaticResolver({
tokens: [/* service callers */],
}),
createJwtSessionResolver({
issuer: "https://idp.example",
audience: "suaveplan",
}),
]);
```
## Worker-proxy surface
Use the worker-proxy variant when the host process should not hold the
real token table — e.g. an isolated auth worker serving multiple host
workers. The host imports `createBearerStaticResolverWorkerProxy(port)`;
the worker imports `serveBearerStaticResolverOnPort(port, options)`. The
proxy transparently passes `runAuthSessionConformance`, so plugin code
treats it identically to the direct surface.
```ts
// host.ts
const proxy = createBearerStaticResolverWorkerProxy(port);
const chain = new SessionResolverChain([proxy, jwtResolver]);
```
```ts
// worker.ts
serveBearerStaticResolverOnPort(port, {
tokens: [/* real table — never crosses to the host */],
});
```
## Optional mutual-TLS gate
Token entries MAY carry `mtlsRequired: true`. When set, the resolver
requires the host listener to surface a verified peer certificate via
`request.tls.peerCertificate`; absent → returns `null` (outcome:
`mtls-required`). The adapter does NOT establish TLS — that is the host
listener's responsibility (Bun TLS, Node `https`, Cloudflare Worker TLS
terminator, etc.).
## Token rotation
This adapter is intentionally static — rotate tokens by reloading host
config. There is no DB-backed lookup, no JWKS, no refresh loop. If
you need rotation without restarts, use `api-auth-better-auth` or a
future `api-auth-token-table` adapter; this one is the simplest possible
thing that resolves a fixed table against constant-time match.
## Errors
The package exposes the `AUTH_BEARER_STATIC.*` code namespace via
`AUTH_BEARER_STATIC_CODES`. Only `INVALID_OPTIONS` is thrown
(synchronously, at factory construction); `NO_TOKEN`, `UNKNOWN_TOKEN`,
and `MTLS_REQUIRED` are surfaced via the OTel match counter's `outcome`
label.
## Telemetry
One counter — `api.auth.bearer_static_match_total{outcome=match|no-token|unknown-token|mtls-required}`
— increments exactly once per `resolve()` call.
## Conformance
Both surfaces pass `runAuthSessionConformance` from
`@suaveplan/api-auth`. Spec-conformance tests live alongside the source
under `src/factory/*.spec-conformance.test.ts` and
`src/proxy/*.spec-conformance.test.ts`.