Search docs

Jump between documentation pages.

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. Refuses HS* at construction, requires a kid header that matches a JWK in the set, and cross-checks JWT-header algagainst the JWK's declared alg when 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 after ctx.state.user.username is stamped, so handlers do not re-parse the Authorization header.
  • Cache-Control: no-store on every first-party auth helper 401 challenge (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.

ts
import { App } from "@daloyjs/core";
import { jwk } from "@daloyjs/core/jwk";

const app = new App();

app.use(
  jwk({
    algorithms: ["RS256", "ES256"],
    jwks: "https://login.example.com/.well-known/jwks.json",
    issuer: "https://login.example.com/",
    audience: "https://api.example.com",
    fetchTtlSeconds: 600,
    realm: "api",
  }),
);

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.

ts
import { bearerAuth } from "@daloyjs/core";

app.use(
  bearerAuth({
    validate: (token) => verifyOpaqueToken(token),
    verify: async (token, ctx) => {
      const tenantId = ctx.request.headers.get("x-tenant-id") ?? "default";
      return !(await isTokenRevoked(tenantId, token));
    },
  }),
);

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.

ts
import { basicAuth } from "@daloyjs/core";

app.use(
  basicAuth({
    verify: (username, password) => verifyCredentials(username, password),
    onAuthSuccess: async ({ username }, ctx) => {
      ctx.state.authenticatedUser = username;
      await recordBasicAuthSuccess(username);
    },
  }),
);

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.