401 — every route except GET /health requires an authenticated user.
Pattern 1 — Runtime-level resolver (resolveUser)
Configured on createRuntime (or passed through the factory options). It receives the translated
request — the adapter’s RuntimeRequest shape — so you read request.headers, request.query,
etc. Use this when your auth already works off raw headers (session cookies, JWTs):
resolveUser may be async; null and thrown errors are equivalent (both produce 401).
Pattern 2 — Express middleware pattern (resolveUserFrom)
If your stack already authenticates users in Express middleware (Clerk, Passport, a JWT check)
and attaches the identity to req, you can resolve from the raw Express Request instead. The
adapter runs your resolver after translation, before handing the request to the runtime:
resolveUserFrom only sees whatever is on req at that
point.
Why both?resolveUserFromis the ergonomic choice when your auth is Express-native.resolveUseris the general choice — it also works if you ever mount the same runtime via a different transport. If you provide both,resolveUserFromwins (it’s applied last, and the runtime-level resolver becomes a pass-through of the identity it resolved).
Unauthenticated requests
WhenresolveUserFrom returns null (or resolveUser returns null or throws), the runtime
responds 401 with the envelope:
Using your own identity vs forwarding client input
The adapter never passes through raw identity from the browser.resolveUserFrom reads whatever
your middleware verified and attached — Clerk’s req.auth.userId, Passport’s req.user.id,
your JWT payload — and that verified id is what Persona scopes the request to. Never derive it
from an untrusted header the client could forge; always go through a step your backend actually
authenticated.
Health is public
GET /health does not call the resolver at all — it’s a liveness/capability probe backed by the
SDK’s whoami(). It’s the one route you can mount without any auth in front. See
Runtime routes for the full always-on table.