Skip to main content

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:

GuardBehaviorUsed 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_DOMAIN set 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

StatusMeaningBody
401Missing/invalid/expired token (on a required route).{ "error": "Missing or invalid Authorization header" } or the verifier's message.
403Authenticated but lacking the required role.{ "error": "Insufficient permissions" }
500Auth0 expected but not configured server-side.{ "error": "Auth0 not configured" }

See Errors for the full model.

Environment variables

VariablePurpose
AUTH0_DOMAINTenant domain, e.g. your-tenant.auth0.com. Unset ⇒ dev mock mode.
AUTH0_AUDIENCEExpected token audience, e.g. https://api.digitshealth.com.
AUTH0_CLIENT_IDClient identifier (used by the web app).