Executive Summary

Authentication and authorization are the two controls that make a FHIR backend safe to put in front of patients, clinicians, and AI agents. The two controls answer different questions. Authentication answers “who is making this request”; authorization answers “what is this requester allowed to do.” A backend that conflates the two, or that solves one without the other, has gaps that an audit will find.

The patterns that work in production are well-known but rarely described together in one place. This paper consolidates the patterns we see most often in regulated digital health products, organised by the question they answer and the failure mode they prevent.

The audience is engineering and security leads building a FHIR backend or evaluating one. The paper assumes familiarity with FHIR, OAuth 2.0, OIDC, and the SMART on FHIR launch profiles. It does not introduce these standards from scratch; it focuses on how to combine them into something a regulator and an auditor will both accept.


1. Authentication Patterns

Three authentication contexts cover almost every production case: end users, integration partners, and AI agents.

1.1 End-user authentication

End users (patients, clinicians, support staff, administrators) authenticate against a managed identity provider through OAuth 2.0 / OIDC. The backend validates the access token, extracts the subject claim, and resolves the subject to a stable identifier inside the data model.

Bind the authenticated subject to a FHIR resource: a Patient for a patient, a Practitioner for a clinician, a RelatedPerson for a caregiver, a Device for a server-to-server identity. The binding is what makes audit attributable in clinical terms: the audit log records “Patient/123 read Observation/456” rather than “user opaque-uuid read Observation/456.”

Multi-factor authentication is enforced at the identity provider, not at the backend. The backend trusts the IdP’s claim that MFA was satisfied; the IdP is the single place to configure the MFA policy.

1.2 Integration partner authentication

Integration partners (TI connectors, payer systems, analytics pipelines, study data exporters) authenticate as service accounts. OAuth 2.0 client credentials with a per-partner client and a per-partner scope is the standard shape.

Each service account should have a documented owner, a documented scope, and a documented rotation policy. The audit log records the service account as the resolved identity; downstream code sees the same audit shape it sees for an end user.

1.3 AI agent authentication

AI agents are a third authentication context that has emerged in production over the last two years. An AI agent runs autonomously against the backend, often through a Model Context Protocol (MCP) server or a direct API integration. The agent is not a person; it is a service that acts on behalf of a person or on behalf of an organisation.

Authenticate the agent as a service account but carry the on-behalf-of identity in the request context. The backend’s authorization rules then evaluate against the on-behalf-of identity, and the audit log records both the agent and the on-behalf-of identity. The Fire Arrow Agentic LLM Access whitepaper describes this pattern in more depth.

1.4 Failure modes

The most common authentication failure mode is to authenticate the user but never to bind the user to a FHIR identity. The audit log then records opaque user IDs that have no clinical meaning; the access boundary is hard to defend; and the patient-side access-log request is hard to fulfill.

The second-most common failure mode is to share a service account across integration partners. When something goes wrong, the audit log cannot tell which partner did it. The fix is to issue per-partner service accounts and to pay the small operational cost of more accounts in exchange for the audit clarity.


2. Authorization Patterns

Authorization is harder than authentication because the rules are richer and the failure modes are more numerous.

2.1 Default-deny rule chain

The architectural foundation is a default-deny rule chain at the data layer. A request that does not match an explicit grant is denied. The grants are evaluated in sequence; the first match wins.

A default-deny chain expresses the access boundary as a property of the system, not as a property of the application code that scattered checks would distribute through the codebase.

2.2 Role-based grants tied to clinical relationships

Each role expresses a clinical relationship. A patient role grants access to the resources in the patient’s compartment. A clinician role grants access to the patients within the clinician’s care relationships. A support role grants narrowly-scoped access to specific resources for support purposes.

The grants are typically expressed as a small number of rule predicates. A patient grant is roughly “subject is the resource owner.” A clinician grant is roughly “subject has a CareTeam membership covering the resource owner.” A support grant is roughly “subject has a SupportTicket covering the resource owner.”

2.3 Search narrowing

When a role queries a resource type, the search must be narrowed to the resources the role is allowed to see. Narrowing happens at the database layer, not at the application layer that filters results post-hoc.

The reason is correctness. A search that filters post-hoc will return wrong page sizes, wrong total counts, and wrong sort orders. A search that narrows at the database layer returns the correct result set as if the role only ever had access to that subset of the database.

2.4 Property-level redaction

Some roles need access to a resource type but not to all of its fields. A research role needs Observation values but not patient identifiers. A support role needs Patient demographics but not telecoms. Per-role property filters strip the disallowed fields from the response.

Property filters are particularly important for analytics, sponsor, and research access. They allow the same database to support multiple access scopes without copying data into a separate store.

2.5 Search-parameter blocklists

Property filters alone create a side channel: a role that cannot see a field can still search by it and infer its value from the result set. The fix is per-role search-parameter blocklists that prevent the role from searching by fields it cannot see.

The combination of property filters and search-parameter blocklists is what closes the side channel. Either alone is not enough.

2.6 SMART on FHIR scopes

For end-user roles authenticated through SMART on FHIR, the scope claim narrows the access further. A scope of patient/Observation.r grants read access to Observations in the patient’s compartment. A scope of user/*.r grants read access to everything the user is authorised for, narrowed by the per-role rules above.

Treat the scope as an additional narrowing on top of the role grant, never as a widening. A scope cannot grant more than the role allows; it can only grant less.

2.7 Failure modes

The most common authorization failure mode is scattered checks in application code. The check is correct in most places, missing in one, and the missing place is the gap an attacker or an auditor finds. The fix is to move authorization to the data layer.

The second-most common failure mode is property filters without search-parameter blocklists. The role cannot see the field but can search by it; the field’s value leaks through the search. The fix is to combine property filters with blocklists.

The third-most common failure mode is to grant write access by symmetry with read access. A role that can read a resource is granted write access by default. The fix is to grant write access explicitly per role, and to default to read-only.


3. The SMART on FHIR Launch Patterns

SMART on FHIR defines two launch patterns that cover most end-user contexts: the EHR launch and the standalone launch.

3.1 EHR launch

The EHR launch pattern is used when an app launches from inside an EHR. The EHR redirects to the app’s launch endpoint with a launch context (typically a patient and an encounter). The app exchanges the launch context for an access token through the EHR’s authorization server. The token includes the patient and encounter context that narrows the access.

On the backend side, honour the launch context through the SMART scope mechanism: a launch/patient scope tied to the patient context, a launch/encounter scope tied to the encounter context. The backend’s authorization rules evaluate against the context.

3.2 Standalone launch

The standalone launch pattern is used when an app launches outside of an EHR. The user authenticates directly against the backend’s identity provider; there is no launch context to exchange. The access token includes the user’s claims; the backend’s role rules evaluate against the claims.

Treat standalone launch as the default end-user authentication on the backend side, and handle the EHR launch as an additional case where the launch context narrows the access.

3.3 Backend services

SMART on FHIR also defines a backend services launch pattern for service-to-service authentication. The pattern uses asymmetric client credentials (a JWT signed by the client’s private key) and grants the client a scope appropriate for backend service access.

Use backend services for integration partners that support it, and OAuth 2.0 client credentials for partners that do not. The two patterns produce the same audit shape on the backend side.


4. Audit Patterns

Audit is the third leg of the access boundary, alongside authentication and authorization. An access boundary that does not produce identity-attributable audit records is not auditable.

4.1 Identity-attributable audit on every access

Each authorised request emits an audit record with the resolved identity, the operation, the resource, and the rule that authorised it. The audit covers REST, GraphQL, bulk operations, and any other API surface uniformly.

Emit the audit record from the data-access layer, not from the application code that orchestrates the request. The data-access layer sees every request, and the audit becomes a property of the system rather than a property of each endpoint.

4.2 Audit storage

Audit records should be stored in an append-only log with documented retention. The retention period depends on the regulatory context: two years for general security audit, longer for medical-device traceability, longer still for some payer-side requirements.

Write audit records to a separate store from the operational database. The audit store can have a different operational profile (write-heavy, read-rare) and can be retained on cheaper storage with longer retention.

4.3 Patient-side access logs

Some regulations (the German DiGA framework most explicitly) entitle the patient to a log of who accessed their data. Store the audit record with the resolved identity and the resource, and expose a patient-scoped query that returns the records for the patient’s resources.

The query is a narrow case of the same audit log; it does not require a separate data structure.


5. Token Patterns

Three token-related patterns matter most for production deployments.

5.1 Short-lived access tokens with refresh

Access tokens should be short-lived (minutes, not hours) with refresh tokens used to extend the session. Short-lived access tokens limit the blast radius of a leaked token; refresh tokens allow the IdP to revoke the session centrally.

5.2 Token introspection vs JWT validation

The backend can validate access tokens through introspection (calling the IdP for each token) or through JWT validation (verifying the signature locally). JWT validation is faster but cannot honour mid-session revocation. Use JWT validation for the common case and introspection for elevated-privilege operations where the freshness matters.

5.3 Per-resource access tokens

Some access patterns benefit from per-resource access tokens (signed URLs for binary downloads, time-limited tokens for one-off exports). The backend issues the token, the client uses it for the specific resource, and the token expires shortly after. The pattern reduces the surface of bearer credentials in flight without rebuilding the authentication stack.


6. The MCP and AI Agent Authorization Pattern

AI agents have shown up as a new authentication and authorization context in the last two years. Treat the agent as a service account with an on-behalf-of identity carried in the request context, and evaluate the authorization rules against the on-behalf-of identity.

The MCP (Model Context Protocol) server is typically the layer that sits between the agent and the backend. The MCP server authenticates the agent, resolves the on-behalf-of identity, and translates the agent’s intent into FHIR operations. The backend treats the MCP server as the authenticated entity and evaluates the authorization rules against the on-behalf-of identity in the request context.

The audit log records both the MCP server and the on-behalf-of identity. The audit shape is consistent with the audit shape for end users; the difference is that the audit record carries both identities.

The pattern is described in more depth in the Fire Arrow Agentic LLM Access whitepaper.


7. Bulk Data Export Authorization

Bulk Data export is a special case because the operation produces a large dataset that bypasses the per-resource access checks the rule chain enforces.

Evaluate the access rules at the export-job-creation step, narrow the export by the role’s scope, and emit a single audit record covering the entire export. The export job runs against the narrowed dataset; the audit covers the export rather than each resource it produces.

The pattern’s failure mode is to allow the export to bypass the rule chain entirely. The fix is to treat Bulk Data as a first-class operation that the rule chain narrows.


8. Common Mistakes

Across deployments three mistakes account for most of the audit findings we see.

8.1 Authentication without identity resolution

The user is authenticated but never bound to a FHIR identity. The audit log records opaque user IDs. The access boundary is correct on paper but not auditable in practice.

Fix: bind the authenticated subject to a FHIR resource at the boundary.

8.2 Authorization scattered through application code

Authorization checks are scattered through the application code. The check is correct in most places, missing in one, and the missing place is the gap.

Fix: move authorization to the data layer; express the rules as a default-deny rule chain.

8.3 Property filters without search-parameter blocklists

Property filters strip fields the role cannot see, but the role can still search by the fields. The values leak through search.

Fix: combine property filters with per-role search-parameter blocklists.


9. Closing

Authentication and authorization for a FHIR backend are well-trodden ground. The patterns that work are documented and consistent: managed IdP for authentication, identity binding to FHIR resources, default-deny authorization at the data layer, identity-attributable audit on every request.

What separates a backend that survives an audit from one that does not is the discipline of treating the access boundary as a property of the system rather than as a property of each endpoint. The patterns above describe that discipline in concrete form.


References