401 — every route except GET /health requires an authenticated user.
Pattern 1 — Runtime-level resolver (resolveUser)
Configured on PersonaModule.forRoot(). 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 — NestJS request-level resolver (resolveUserFrom)
If your stack authenticates users via NestJS guards and attaches the identity to the raw request, you can resolve from the NestJS Request object instead. The adapter runs your resolver after translation, before handing the request to the runtime:
PersonaModule uses configure() to register PersonaMiddleware on the routePrefix, any guard on the same routes runs after the middleware. To ensure your auth runs first, either:
- Use a global guard with a higher priority, or
- Use the runtime-level
resolveUserinstead (it reads from the translated request, which has all headers available).
userId to the raw NestJS request via a guard, resolveUserFrom reads it from there.
Why both?resolveUserFromis the ergonomic choice when your auth is NestJS-native (guards, decorators).resolveUseris the general choice — it also works if you ever need to resolve from the translated request shape. 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:
UnauthorizedException), do it before the adapter middleware — the adapter itself never rejects; it only lets the runtime answer.
Using your own identity vs forwarding client input
The adapter never passes through raw identity from the browser.resolveUserFrom reads whatever your guard verified and attached — req.userId from a JWT guard, req.user.id from Passport, etc. — 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 reach without any auth in front.