first commit
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# jwks-endpoint
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides a framework-agnostic JWKS endpoint backed by a `TenantKeyStore`. The endpoint serialises active public keys from the store into a `{ keys: JWK[] }` JSON Web Key Set and exposes an HTTP response builder that any framework can consume. An internal cache reduces key-store reads on high-traffic endpoints, with the TTL controlled by `cacheMaxAge`.
|
||||
|
||||
## Features
|
||||
|
||||
- `JWKSEndpoint` class with configurable cache TTL and injectable clock
|
||||
- `getJWKS(config?)` — fetches active public keys and returns a `JWKS` object
|
||||
- `handleRequest(request)` — returns a framework-agnostic `{ status, body, headers }` response
|
||||
- `clearCache()` — invalidates the internal JWKS cache on demand
|
||||
- `formatJWKSResponse(keys)` — standalone pure function to wrap JWK arrays
|
||||
- `createJWKSEndpoint(options)` — factory function
|
||||
- `includeExpired` option to bypass cache and include all keys
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { InMemoryKeyStore } from "@suaveplan/jwt-tenant/jwks/in-memory-key-store";
|
||||
import { JWKSEndpoint } from "@suaveplan/jwt-tenant/jwks/jwks-endpoint";
|
||||
import { generateKeyPair } from "@suaveplan/jwt-tenant/keys/key-generator";
|
||||
|
||||
const store = new InMemoryKeyStore();
|
||||
const kp = await generateKeyPair("EdDSA");
|
||||
await store.addKey(kp);
|
||||
|
||||
const endpoint = new JWKSEndpoint({ keyStore: store });
|
||||
const response = await endpoint.handleRequest(null);
|
||||
// response.status === 200
|
||||
// JSON.parse(response.body).keys.length === 1
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { JWKSEndpoint } from "@suaveplan/jwt-tenant/jwks/jwks-endpoint";
|
||||
|
||||
// Use in a Hono or Express route
|
||||
app.get("/.well-known/jwks.json", async (req, res) => {
|
||||
const response = await endpoint.handleRequest(req);
|
||||
res.status(response.status)
|
||||
.set(response.headers)
|
||||
.send(response.body);
|
||||
});
|
||||
|
||||
// Force cache invalidation after key rotation
|
||||
await store.addKey(newKeyPair);
|
||||
endpoint.clearCache();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `JWKSEndpoint`
|
||||
|
||||
Constructor: `new JWKSEndpoint(options: JWKSEndpointOptions)`.
|
||||
|
||||
- `options.keyStore` — `TenantKeyStore` implementation
|
||||
- `options.cacheMaxAge` — cache TTL in milliseconds (default 5 min)
|
||||
- `options.clock` — injectable clock for cache expiry
|
||||
|
||||
#### Methods
|
||||
|
||||
- **`getJWKS(config?): Promise<JWKS>`** — Returns active public keys as a JWKS. Caches result unless `config.includeExpired` is `true`.
|
||||
- **`handleRequest(request): Promise<JWKSResponse>`** — Returns `{ status: 200, body: string, headers }`. The `request` parameter is accepted for interface compatibility.
|
||||
- **`clearCache(): void`** — Resets the internal cache immediately.
|
||||
|
||||
### `formatJWKSResponse(keys: JWK[]): JWKS`
|
||||
|
||||
Pure function that wraps a `JWK[]` in a `{ keys }` envelope.
|
||||
|
||||
### `createJWKSEndpoint(options): JWKSEndpoint`
|
||||
|
||||
Factory function; equivalent to `new JWKSEndpoint(options)`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The cache uses a simple timestamp check against `clock()`. Cache invalidation on key rotation must be explicit (`clearCache()`). The `handleRequest` `_request` parameter is intentionally ignored — the endpoint serves the same JWKS for all requests. In production, mount the endpoint behind a CDN with short TTLs for automatic key rotation propagation.
|
||||
Reference in New Issue
Block a user