← Blog
Field note

SMART Backend Services: Server and AI Agent Auth for FHIR

Thien Nguyen · Nov 11, 2026

SMART Backend Services is how a server, or an AI agent's tool layer, calls a FHIR API with no user present: register a public key (JWKS), sign a JWT that expires within 5 minutes, exchange it for a token that should live at most 300 seconds, and request narrow SMART v2 system/ scopes, never system/*.cruds.

Backend services used to be a niche: nightly exports and population analytics. In 2026 they are on most integration roadmaps, because AI agents, prior-authorization automation and payer-provider data exchange all need server-to-server access. ONC's HTI-1 rule made SMART App Launch 2.0.0 the floor for certified (g)(10) APIs from December 31, 2025, and the current published version of the spec is SMART App Launch v2.2.0. This guide is for engineers and CTOs who have to get it right: the protocol, the scope design, key management, and the agent-specific failure modes.

What is SMART on FHIR Backend Services?

It is the SMART profile for clients that run "autonomously (or semi-autonomously)" with access pre-authorized by the FHIR server. It uses the OAuth 2.0 client-credentials grant with asymmetric (public key) client authentication. There is no user login and no redirect.

The spec says it fits when the client runs without user-level authorization, supports asymmetric authentication, and there is "no compelling need" for a user to authorize access at runtime. Typical uses: bulk export, data pipelines, scheduled jobs, and the server side of an AI agent that works on queues rather than on behalf of a signed-in clinician.

EHR launch / standalone launchBackend Services
Who authorizesA user (clinician or patient) at runtimeThe FHIR server's administrator, in advance
GrantAuthorization code (+ PKCE)Client credentials with a signed JWT assertion
Typical scopesuser/ or patient/system/
Audit attributionThe signed-in userThe client (often mapped to a service user by the EHR)
Best forInteractive apps and assistants in a clinician's workflowBatch jobs, exports, queue workers, agent tool layers

How does the Backend Services token flow work?

Four steps: discover the server's .well-known/smart-configuration, build and sign an authentication JWT with your private key, POST it to the token endpoint with grant_type=client_credentials, and call the FHIR API with the short-lived bearer token you get back.

  1. Discovery. Read token_endpoint, token_endpoint_auth_methods_supported (must include private_key_jwt) and token_endpoint_auth_signing_alg_values_supported (must include at least one of RS384 or ES384), per the asymmetric client authentication page.
  2. Authentication JWT. Header: alg, kid (unique within your JWK Set), typ: JWT, optional jku. Claims: iss and sub = your client_id, aud = the token URL, exp no more than five minutes in the future, and a unique jti.
  3. Token request. Form-encoded POST with scope, grant_type=client_credentials, client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer and client_assertion.
  4. Use the token. The response has access_token, token_type: bearer, expires_in and scope. The granted scope "can be different from the scopes requested," so check it before you run the job.

On lifetime, the spec is explicit: access tokens "SHALL be short-lived; the expires_in value SHOULD NOT exceed 300," which is five minutes. Request new tokens as needed instead of caching one for an hour.

Four steps of SMART Backend Services: 1 discover the token endpoint and supported algorithms (private_key_jwt with RS384 or ES384); 2 sign a JWT with iss and sub equal to the client_id, aud equal to the token URL, exp at most 5 minutes ahead and a unique jti; 3 POST a client_credentials request with narrow system scopes; 4 check the granted scope and use the token for up to 300 seconds.
No user, no redirect: a signed JWT in, a five-minute token out.

Which signing algorithms and keys should you use?

Use ES384 or RS384, published through a JWKS URL with a stable kid per key, separate key pairs per environment, and a rotation process that adds the new key before using it. Check each EHR's own rules, because they differ.

The SMART spec requires servers to support at least one of RS384 or ES384. EHR implementations add their own constraints. For example, Epic's OAuth 2.0 specification accepts RS256, RS384 and RS512 for all confidential clients and ES256 and ES384 only for clients that register a JWK Set URL, requires jti of at most 151 characters, and will not accept a non-production JWKS URL as the production URL for new apps.

Key management rules we apply

  • JWKS URL over static upload. It makes rotation a config change, not a support ticket. The spec says servers "SHALL NOT cache a JWKS for longer than the client's cache-control header indicates," so set that header deliberately.
  • Private keys in a KMS or HSM, never in an environment variable or container image. Sign through the KMS API.
  • Rotation in three steps: publish the new public key, wait out the JWKS cache period, switch signing to the new key, then remove the old key after the longest token lifetime has passed.
  • One key pair per environment and per tenant boundary. If production and staging share a key, a compromised staging box can impersonate production.
  • Clock discipline. With a 5-minute window on exp, clock skew of a few minutes breaks auth intermittently. Run NTP and log the server's error body.
Four numbers for SMART Backend Services: JWT exp no more than 5 minutes ahead; access token lifetime should not exceed 300 seconds; servers must support at least one of RS384 or ES384, and Epic also accepts RS256, RS512 and ES256; Epic limits jti to 151 characters with no reuse while the JWT is valid.
Short windows make clock skew and jti reuse the top causes of 401s.

How should you design SMART v2 scopes for a backend service?

Request the smallest set of resource-level scopes using v2 syntax (create, read, update, delete, search), split read and write into different clients where you can, and use granular query-parameter scopes only where the server documents support for them.

From the SMART scopes specification:

  • v2 permissions. system/Observation.rs means read and search Observations. v1 scopes map as .read → .rs, .write → .cud, .* → .cruds.
  • Granular scopes. A query suffix narrows access, for example patient/Observation.rs?category=http://terminology.hl7.org/CodeSystem/observation-category|laboratory for lab results only.
  • Experimental features. The spec lists search modifiers, chained parameters and _filter in scopes as experimental "even if they are supported by a server." Do not build your security model on them.
  • Batch and transaction. These are validated "based on the actual requests within them," so a transaction does not bypass scopes.

A scope pattern that survives a security review

ClientScopesWhy separate
Reader (pipelines, agent context)system/Patient.rs system/Encounter.rs system/Condition.rs system/Observation.rsMost jobs only read; a leak exposes data but cannot change it
Writer (approved actions only)system/DocumentReference.c system/Task.cuWrite path is small, audited, and only called after human approval
Bulk exporterResource-level .rs scopes for the exported typesLong-running jobs; isolate rate limits and keys

Resource names here are illustrative. Use what each EHR documents as supported for system/ access, which varies by vendor and version.

Should an AI agent use Backend Services or user-context tokens?

Use user-context tokens (EHR launch) when the agent acts for a signed-in clinician, so access and audit follow that person. Use Backend Services for queue-driven work that no single user triggers, and keep the token in a tool layer the model cannot see.

The risk with backend tokens for agents is simple: a system/ token sees every patient the client is pre-authorized for, and the EHR's audit log attributes calls to the client, not to the person who asked the agent a question. Prompt injection or a planning error then turns into a population-level data exposure. Controls we put in front of every backend token an agent can reach:

  • Token broker. The model calls tools; tools call a broker; only the broker holds keys and tokens. The model never sees a token or a raw FHIR URL.
  • Per-request patient binding. The tool accepts a patient ID only from the case or queue item being worked, and rejects queries outside it.
  • Allow-listed operations. Each tool maps to fixed FHIR queries. No free-form search strings from the model.
  • Human approval before writes and before anything leaves the organization.
  • Your own audit trail that records the human requester, the agent run, the tool call and the FHIR request ID, because the EHR's log will only show the client.
  • Minimum-necessary data to the model: fetch broadly if you must, but pass only the fields the task needs.

If you expose FHIR tools to agents through MCP, the same rules apply to the MCP server: it holds the credentials, enforces the allow-list and logs every call. See MCP server development and our AI agent security checklist.

What are the most common Backend Services failures?

Clock skew, reused jti values, wrong aud, JWKS caching surprises during rotation, assuming the granted scope equals the requested scope, and caching access tokens past their lifetime. Each produces intermittent 401s that are hard to reproduce.

  • aud mismatch: it must be the token endpoint URL. Some servers allow extra audiences; Epic, for example, lets customer administrators add audience URLs to an allow-list.
  • jti reuse: the server "SHALL" check that a jti has not been seen for the same issuer within the JWT lifetime. Retrying a failed request with the same JWT fails for this reason. Generate a new JWT per attempt.
  • Silent scope downgrade: the response's scope may be narrower than requested. Fail closed if a required scope is missing.
  • Per-customer setup: EHRs may need site-level configuration before a backend client works, such as mapping it to a service user for audit. Sandbox success says nothing about that step.
  • Rate limits on the token endpoint: requesting a new token for every FHIR call can hit limits. Reuse a token for its (short) lifetime.

How do you test a Backend Services integration before go-live?

Test the unhappy paths deliberately: expired JWT, reused jti, wrong kid, rotated key, narrowed scope, revoked client, and 429 from the token endpoint. Then run the same suite against each customer's non-production environment, not just the vendor sandbox.

For AI features, add an evaluation set that proves the agent never requests data outside the bound patient, never calls a write tool without an approval record, and degrades safely when the token broker refuses. That evaluation belongs in CI, not in a one-time pen test. Our guide on LLM testing for regulated industries covers how we build those suites. For bulk exports built on the same auth, see bulk FHIR export for analytics and AI.

Does SMART Backend Services satisfy HIPAA by itself?

No. It is an authentication and authorization mechanism. HIPAA still requires a BAA with every business associate handling PHI, access controls, audit controls and minimum-necessary use, and those sit in your architecture, not in the token.

A well-scoped backend client helps you show the minimum-necessary and access-control parts. You still need logging, retention, incident response and vendor agreements for your cloud, model provider and anyone else in the data path. See HIPAA-compliant AI agents for the full set of controls.

How BeevR builds this

We build backend integrations the way a security reviewer expects: JWKS-published keys held in a cloud KMS, per-environment key pairs, scripted rotation, split reader and writer clients, a token broker in front of any AI component, and an audit trail that names the human behind every agent action. We test the failure paths in CI and against your customers' non-production environments. BeevR signs a BAA as a business associate, AI models can run inside your own AWS account under the AWS BAA, and you own the source code and IP from day one.

Our senior, founder-led team in Hanoi has a multi-tenant EHR live across four US clinics at about 300 patients a day, and you see a working demo every Friday. Work runs in fixed-scope, fixed-price phases from $10K per phase. See our EHR integration services and HIPAA-compliant AI agent work.

Connecting a server or an AI agent to an EHR? Book a 30-minute call and we will review your auth flow and scope design with you.