Xenrad Docs

OAuth2, API keys, and client assertions

Step-by-step SMART-style client authentication—create a site API key, configure JWKS, scopes, audiences, and exchange a client assertion for an access token.

Xenrad integration uses OAuth 2.0 client credentials with a private_key_jwt style client assertion (JWT bearer). There are no shared client secrets issued by the server. Your system proves identity by signing a JWT with a private key whose public key is registered as JWKS.

Prerequisites

  • Admin access in the Xenrad web app to the site you are integrating.
  • Permission to create and manage API keys for that site (site.api_keys.* style permissions in the product).
  • An asymmetric key pair (for example RSA or EC) on the integration side to sign assertions.

Step 1 — Generate a key pair (on the integrator side)

  1. Create an RSA or EC key pair in a secure store (HSM, vault, or local dev key for testing only).
  2. Publish the public half as a JWKS document with a keys array (standard JWK format).
  3. Note the kid (key id) if you rotate keys; your JWT header should reference the correct key.

You will either host the JWKS at an HTTPS URL or paste the JWKS JSON into Xenrad when creating the key.

Step 2 — Create an API key in the Xenrad UI

Navigate to the siteAPI keys (labels may read “API keys” or “integration clients”).

Click Create API key and fill the form. After save, the table shows a Client ID (for example xenrad-…). Copy and store it; it is the OAuth iss / client identifier for assertions.

Field reference (every field)

FieldRequiredMeaning
NameYesHuman-readable label (for example Epic FHIR prod). Shown only in admin UI.
StatusYesactive or disabled. Disabled keys cannot obtain tokens.
JWKS URIOne of URI or inlineHTTPS URL returning {"keys":[...]}. The server can fetch your public keys from here.
Inline JWKS JSONOne of URI or inlinePaste the same JSON you would serve at the URI. Use when the integrator cannot host JWKS.
Token TTL (seconds)YesLifetime for access tokens Xenrad issues after a successful exchange. Allowed range is 60–3600 seconds; the server enforces bounds.
Allowed scopes (comma-separated)Strongly recommendedScopes this client may request at the token endpoint. Only scopes listed here can appear on the granted token. Use SMART system scopes (see below).
Allowed audiences (comma-separated)OptionalValid aud values for access tokens Xenrad issues. Used with the audience parameter on the token request. If you omit the token request’s audience, the server may fall back to the first entry here or an internal default—set this explicitly for clarity.
Protocols (comma-separated)Yes for non-FHIRExamples: fhir, hl7, hl7-mllp. Default is fhir if empty on create. Controls which integration surfaces treat this client as eligible (MLLP also checks for hl7-mllp).
Allowed IPs (comma-separated)For HL7 MLLPRemote IP rules for MLLP connections. Supports single IPs, optional CIDR ranges, or * for “any” (use with care). Not used for FHIR HTTP or HL7 HTTP (those use bearer tokens).
HL7 sender applicationOptional MLLP filterIf set, MLLP messages must match MSH-3 sending application.
HL7 sender facilityOptional MLLP filterIf set, MLLP messages must match MSH-4 sending facility.

Either JWKS URI or inline JWKS is required on create. The server rejects a key with neither.

Scopes you can allow

The admin form may show example placeholders such as fhir.read; the token server only grants SMART-style system scopes. Enter real values like system/Patient.read (comma-separated in the form).

The token server advertises support for SMART-style system scopes (read/write per resource):

  • system/Patient.read, system/Patient.write
  • system/Condition.read, system/Condition.write
  • system/AllergyIntolerance.read, system/AllergyIntolerance.write
  • system/Observation.read, system/Observation.write
  • system/Procedure.read, system/Procedure.write
  • system/DiagnosticReport.read, system/DiagnosticReport.write
  • system/DocumentReference.read, system/DocumentReference.write

Grant only what the interface needs. FHIR checks a read or write scope per resource and method. HL7 HTTP requires write scopes on Patient, Observation, and DocumentReference for the token (see HL7).

Audiences you should set

Access tokens are JWTs with an aud claim. Different listeners validate different audiences:

  • FHIR HTTP expects audience xenrad-fhir when verifying tokens.
  • HL7 HTTP expects xenrad-hl7-http.

Put the audiences you need into Allowed audiences, and pass the right one as audience when you POST to the token endpoint (recommended). If you rely on the default list, ensure the first allowed audience or the explicit audience parameter matches the surface you call.

Step 3 — Fetch SMART configuration

GET /.well-known/smart-configuration on the OAuth2 base (see Endpoints reference).

You should read at least:

  • issuer
  • token_endpoint
  • token_endpoint_auth_methods_supported (expect private_key_jwt)
  • scopes_supported

Step 4 — Build the client assertion JWT

Create a JWT with:

  • alg — match a key your JWKS advertises (for example RS256).
  • typJWT.
  • kid — optional but recommended to select the signing key.

Claims (standard pattern for this integration):

ClaimValue
issYour Client ID from Xenrad (for example xenrad-uuid…)
subSame as iss
audThe token endpoint URL exactly as you will POST to (must match server validation)
jtiUnique id for this assertion (replay protection)
expShort expiry (assertion is one-time use after validation)

Sign with your private key.

Step 5 — Request an access token

POST to token_endpoint with Content-Type: application/x-www-form-urlencoded:

ParameterValue
grant_typeclient_credentials
scopeSpace-separated list of requested scopes (subset of allowed scopes)
client_assertion_typeurn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertionThe JWT from step 4
audienceOptional but recommended: target resource audience, e.g. xenrad-fhir or xenrad-hl7-http

Successful response JSON includes:

  • access_token — Bearer token for FHIR or HL7 HTTP
  • token_typeBearer
  • expires_in — seconds
  • scope — granted scopes (may be subset of request)

Step 6 — Call the API

Authorization: Bearer <access_token>

Use the token only for the surface whose audience you selected. Fetch a new token when expired.

Operational tips

  • Rotate keys by adding a new JWK to your JWKS with a new kid, then updating the integration to sign with the new key, then removing the old JWK after traffic moves.
  • Disable a key in the UI immediately if a private key leaks; that stops token issuance without deleting audit history (if your deployment retains it).
  • Replay: each client assertion must use a fresh jti; the server rejects duplicates.

On this page