Auth-cohesive slice
Daloy closes the auth-cohesive subset of the leftover items from the secure-by-default initiative. Each one is additive and opt-in:
jwk()— asymmetric-only Bearer-token middleware backed by a JWKS source. RefusesHS*at construction, requires akidheader that matches a JWK in the set, and cross-checks JWT-headeralgagainst the JWK's declaredalgwhen both are present.bearerAuth({ verify })/jwk({ verify })— per-request revalidation hook so revocation lists, token-version counters, and "user changed password since this token was issued" checks can invalidate previously-issued credentials.basicAuth({ onAuthSuccess })— typed-context callback that fires afterctx.state.user.usernameis stamped, so handlers do not re-parse theAuthorizationheader.Cache-Control: no-storeon every first-party auth helper401challenge (bearerAuth(),basicAuth(),jwk()) so intermediaries never cache an auth challenge — RFC 9111 §3.5 and audit alignment.
1. jwk() middleware
Drop-in Bearer-token middleware backed by a JWKS source. The algorithm allowlist is intentionally narrow: only RS256 / RS384 / RS512, PS256 / PS384 / PS512, ES256 / ES384 / ES512, and EdDSA. Symmetric HS*algorithms are refused at construction — the classic confused-deputy "HS256 verified with the JWKS public key as the HMAC secret" attack cannot be configured. The middleware is exported from the dedicated subpath @daloyjs/core/jwk.
jwks accepts a static JwkSet, an https:// URL (with TTL caching and in-flight-promise dedup so a thundering-herd of concurrent requests resolves into a single fetch), or a custom resolver function. http:// JWKS URLs and non-finite / negative fetchTtlSeconds are refused at construction. The middleware stamps ctx.state.user = { sub, scopes, claims }; the scope normalizer reads scope (RFC 6749 space-separated string), scp (Azure AD array), and scopes (array) claims and dedupes the result.
2. Per-scheme verify(credentials, ctx) hook
Both bearerAuth() and jwk() accept an optional verify callback that runs after the static validate / signature check passes. Returning false throws ForbiddenError (403, no WWW-Authenticate per RFC 6750); returning true or undefined accepts. Use it to consult a revocation list, a token-version counter, or any other per-request signal that a previously-issued token has been invalidated.
3. basicAuth({ onAuthSuccess })
Fires once ctx.state.user.username has been stamped, with the typed (credentials, ctx) tuple. The previous idiomatic workaround was a separate beforeHandle that re-parsed the Authorization header in every handler; that is no longer necessary.
4. Cache-Control: no-store on auth 401 challenges
Every first-party auth helper now emits Cache-Control: no-store alongside WWW-Authenticate on the 401 response. A shared CDN, a corporate proxy, or a service-worker cache could previously cache the challenge and serve it to a different user;no-store closes that fingerprinting and stale-challenge risk. This applies uniformly to bearerAuth(), basicAuth(), and the new jwk().
What shipped next
The remaining leftover items — the wsRateLimit() adapter, loginThrottle() preset, rotateSession() helper, the file-upload MIME + magic-byte + size guard, the requirePayloadAuth scheme flag, and the WebSocket-helper safe defaults — shipped in the 0.23.0 remaining slice.