Authentication
The Clinical API authenticates requests with Auth0-issued RS256 JWTs, verified against
the Auth0 JWKS using the jose library. Tokens are passed in the Authorization header:
Authorization: Bearer <access_token>
Implementation: apps/api/src/middleware/auth.ts.
Token requirements
A valid access token must:
- Be signed with RS256 by your Auth0 tenant (verified via
https://${AUTH0_DOMAIN}/.well-known/jwks.json). - Have issuer
https://${AUTH0_DOMAIN}/. - Have audience equal to
AUTH0_AUDIENCE(when configured). - Optionally carry roles in the custom claim
https://digitshealth.com/roles(a string array, e.g.["care_provider"]).
Recognized roles: patient, care_provider, admin.
Auth modes per endpoint
Routes use one of three guards:
| Guard | Behavior | Used by |
|---|---|---|
Required (requireAuth) | 401 if no/invalid token. Sets request.user. | All /api/patients/*, all /api/billing/* (except webhook), /api/lookup/:code/claim. |
Role (requireRole) | requireAuth + 403 unless the user has one of the listed roles. | GET /api/patients and GET /api/patients/:id require care_provider or admin. |
Optional (optionalAuth) | Parses the token if present, ignores it if missing/invalid; never rejects. | Assessment submissions, POST /reports, POST /lookup/anonymous. |
Endpoints with no guard (e.g. GET /api/lookup/:code, GET /api/assessments/:id/results,
biomechanics, TTS, journal) are open.
Why optional auth matters
Assessment submissions accept anonymous users. When you submit without a token, the
record is created against an auto-generated anonymous Contact and is reachable only by its
6-character code (expiring after 90 days). When you submit with a valid token, the same
record is linked to your patient profile and never expires. The result payload is identical
either way — the only difference is ownership.
Development mode (no Auth0)
When the AUTH0_DOMAIN environment variable is unset, the API skips JWKS verification
and injects a mock user so the whole surface is usable locally:
// request.user in dev mode (requireAuth)
{
"sub": "dev|1",
"email": "dev@digitshealth.com",
"name": "Dev User",
"roles": ["patient", "care_provider"]
}
optionalAuth in dev mode only injects the mock user if an Authorization header is
present (with role ["patient"]); otherwise the request stays anonymous. This lets you
exercise both the authenticated and anonymous code paths without real tokens.
Never deploy without
AUTH0_DOMAINset in any environment that holds real patient data.
Getting a token
From the web app (BFF proxy)
Browser code never holds the API token. It calls the same-origin proxy
fetch('/api/proxy/<api-path>'), and apps/web/src/app/api/proxy/[...path]/route.ts pulls
the access token from the Auth0 server session (auth0.getAccessToken()) and forwards the
request with the Authorization header attached.
For server-to-server / native clients
Use the Auth0 OAuth flow appropriate to your client (Authorization Code + PKCE for native
apps, Client Credentials for machine-to-machine) to obtain an access token whose audience
matches AUTH0_AUDIENCE. Then attach it on every request.
Using a token with the SDK
import { DigitsClient } from '@digits/clinical-sdk';
// Static token
const digits = new DigitsClient({ baseUrl, token: accessToken });
// Lazy/refreshing provider — called on every request (sync or async)
const digits2 = new DigitsClient({
baseUrl,
token: async () => (await auth.getAccessToken()).token,
});
The SDK adds the Authorization header automatically when a token is configured.
Error responses
| Status | Meaning | Body |
|---|---|---|
401 | Missing/invalid/expired token (on a required route). | { "error": "Missing or invalid Authorization header" } or the verifier's message. |
403 | Authenticated but lacking the required role. | { "error": "Insufficient permissions" } |
500 | Auth0 expected but not configured server-side. | { "error": "Auth0 not configured" } |
See Errors for the full model.
Environment variables
| Variable | Purpose |
|---|---|
AUTH0_DOMAIN | Tenant domain, e.g. your-tenant.auth0.com. Unset ⇒ dev mock mode. |
AUTH0_AUDIENCE | Expected token audience, e.g. https://api.digitshealth.com. |
AUTH0_CLIENT_ID | Client identifier (used by the web app). |