Replies: 3 comments 1 reply
-
|
Hello Steve, I want to make sure I fully understand the architectural boundary being proposed. From the discussion, my current understanding is:
So in other words:
Is that the intended separation of responsibilities? Additionally, when the discussion says “HFS validates tokens. That is all.” — I interpret that to mean HFS does not perform OAuth flows or issue tokens, but it still owns application-level authorization enforcement. Is that correct? |
Beta Was this translation helpful? Give feedback.
-
|
@smunini , As you did in the Persistence Layer, this is a very well put together document for authorization and authentication. I particularly admire the scopes design and separation of token validation and AuthorizationPolicy. I have done some work on this earlier, and I had gone the implementing an own OAuth server route. (I did read your decisions regarding using open source and commercial authorization servers). I implemented an OAuth 2.1 with PKCE / OIDC-oriented IdP with multi-tenant semantics (organization → hospital) baked into the model, including OAuth client registration/management and user registration tied to client_id (hospital/client context). Redis cache’s for replay protection and session management. What I visualized was that each organization/hospital would have multiple clients. You can have patient-facing apps, a website/SPA, desktop applications for billing staff, service to service API’s (what you are proposing to implement first), etc The authentication flows which are needed -
Some discussion points
|
Beta Was this translation helpful? Give feedback.
-
|
Hi @sandhums,
Thanks!
I have implemented something similar for several clients - including the Redis cache (well, the AWS version of Redis), and there are some good libraries for supporting these standards, however I found there are always updates and various security vulnerabilities to keep on top of. Happily there are some good vendors in this space that provide mature services that focus only on this part of the architecture. It's a put all of your eggs in one basket and keep a close eye on the basket approach.
This is a really good list. Starting with Service to Service API as this is the most urgent need, but I could see us tackling the others on this list as the demand for them is apparent.
Hospitals make an org-level IT decision on their identity platform, and HFS just needs to plug into it.
Yes - that source of truth belongs in the |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Introduction
The question of who can access healthcare data - and under what conditions - has never been more consequential. The same AI-driven forces that are reshaping clinical workflows are also multiplying the number of systems that need programmatic access to FHIR APIs. Analytics pipelines, population health platforms, clinical decision support engines, data integration services - all of them need to connect to FHIR servers without a human user in the loop. Getting authentication and authorization right for these machine-to-machine scenarios is foundational work that every other capability in the Helios FHIR Server will depend on.
This document shares my thoughts on how to approach authentication and authorization for the Helios FHIR Server. Like the persistence layer discussion, this is an architectural strategy document rather than a comprehensive specification. It explains the main motivating direction, the key building blocks, and the Rust trait designs that will form the backbone of our security model.
Who should read this? Anyone with an interest in FHIR security, healthcare interoperability standards, or Rust-based systems design. Feedback is very much welcome - this is open source, developed in the open, and your perspective matters.
The Lay of the Land: What FHIR Says About Security
FHIR itself is not a security protocol. The specification is explicit about this: it defines exchange protocols and data models that must be combined with security protocols defined elsewhere. What FHIR does provide is a clear list of concerns that any production deployment must address - authentication, authorization, audit logging, communications security, and more.
For the Helios FHIR Server, the two most immediate concerns are:
The FHIR security specification recommends OAuth 2.0 as the foundational protocol, and for our use cases specifically points to the SMART App Launch Implementation Guide from HL7. SMART provides two distinct authorization patterns: one for user-facing applications (the "App Launch" flow, which involves a human granting consent), and one for backend services operating without a user (the "Backend Services" flow).
Since the Helios FHIR Server does not yet have a UI, our immediate focus is the SMART Backend Services profile - server-to-server, machine-to-machine authorization. The App Launch profile is explicitly out of scope for this first iteration, though our design should accommodate it cleanly in the future. We will add additional design elements in a comment below in this discussion document when that capability is needed, so stay tuned.
SMART Backend Services: The Essential Flow
Before jumping into Rust traits, it is worth understanding what the SMART Backend Services protocol actually does. Compared to user-facing OAuth flows, it is refreshingly straightforward.
The core use cases are exactly the kind of systems that benefit from a high-performance Rust FHIR server:
None of these involve a user clicking "Allow". Instead, a system administrator configures the trust relationship out-of-band, and the client then autonomously acquires short-lived access tokens to do its work.
The Protocol in Plain Language
Registration (one-time, out-of-band)
Before anything else, the backend client registers with the FHIR authorization server. The most important part of registration is communicating the client's public key. SMART strongly prefers that this be done by pointing to a URL where the client hosts its JSON Web Key Set (JWKS) - this allows key rotation without re-registering. Embedding the key directly is supported but discouraged. At the end of registration, the server assigns the client a
client_id.Discovery
The client fetches
/.well-known/smart-configurationfrom the FHIR server's base URL to learn the token endpoint URL and the server's capabilities. This is a simple HTTP GET that returns a JSON document.Token Request
When the client needs to access data, it constructs a one-time-use JWT (a "client assertion") signed with its private key, then POSTs it to the token endpoint. The server validates the JWT signature against the registered public key, checks that the
jti(JWT ID) hasn't been replayed, and - if everything checks out - issues a short-lived access token. The spec recommends a maximum lifetime of 5 minutes for these tokens.API Access
The client presents the access token as a Bearer token in the
Authorizationheader on every FHIR API request. The resource server validates the token and enforces the scopes it carries.Here is the full flow as a sequence:
A Note on the Authorization Server Relationship
An important architectural decision that we have made: the Helios FHIR Server will not act as an authorization server. Client registration, token issuance, and the OAuth dance belong to a dedicated, independently scalable authorization server. HFS validates tokens. That is all.
This is a decision drawn from our experience building several large-scale systems.
First, operational reality. In production environments at scale, the authorization server and the FHIR resource server have fundamentally different operational characteristics. The authorization server handles short bursts of token requests; the FHIR server handles sustained, high-throughput data queries. They have different CPU and memory profiles, different scaling triggers, different failure modes, and different upgrade cadences. Conflating the two into a single process forces you to make the wrong trade-off for at least one of them.
Second, and more importantly: authorization servers are security-critical infrastructure. The mature open-source and commercial options - Keycloak, Okta, Auth0, Microsoft Entra ID, and others - represent years of battle-tested security engineering. Keycloak alone has over a decade of CVE patches, penetration testing, and hardening by the broader security community. Writing a correct OAuth authorization server requires getting JWT validation, key management, token revocation, replay prevention, timing-safe comparisons, and dozens of other subtle security details exactly right. Getting any one of them wrong can compromise an entire deployment. This is not a place to build from scratch when proven implementations exist.
What you will find in this design, accordingly, is not a custom authorization server. What you will find is a delegation model that integrates cleanly with the established identity providers that organizations already operate. The
AuthProvidertrait abstracts over the token validation contract - whether that means verifying a JWT signature against the authorization server's published JWKS, or calling a token introspection endpoint. Both paths produce aPrincipalthat HFS can reason about. The source of truth for client registration, scope grants, and token issuance remains with the external authorization server throughout.Decoupling also makes the authorization server independently replaceable. A self-hosted Keycloak today, a managed cloud identity provider tomorrow - none of these changes should require touching HFS. The integration point is a well-defined HTTP contract: HFS receives a token, validates it against the authorization server's published key material, and makes an access control decision based on the result.
This is consistent with every serious production deployment we have encountered.
Scopes: The Language of Authorization
SMART defines a scope syntax that maps directly onto FHIR operations and resource types. For backend services, all scopes are
system/scopes - there is no patient or user context. Some examples:system/*.rssystem/Patient.rssystem/Observation.rsystem/Condition.crudThe
r= read,s= search,u= update,c= create,d= delete convention was introduced in SMART v2. The older v1 syntax (.read,.write,.) is also in use in the wild and must be understood.Scopes form the bridge between authentication ("this is client X") and authorization ("client X may do Y"). After token validation, the scope string is the primary input to access control decisions.
The Two Faces of Key-Based Authentication
SMART Backend Services relies exclusively on asymmetric authentication - the
client-confidential-asymmetricprofile. There is no shared secret. The client holds a private key, registers the corresponding public key with the server, and proves identity by signing JWTs.The supported algorithms are
RS384(RSA with SHA-384) andES384(ECDSA with P-384). Servers must support at least one; clients must support both.Key verification works as follows: the server looks at the
jkuheader in the client's JWT (an optional URL pointing to the client's JWKS endpoint) or falls back to the JWKS URL registered at enrollment time. It fetches the JWKS, finds the key matching thekidheader, and verifies the signature. The server MUST NOT cache the JWKS longer than the client'sCache-Controlheader indicates - this is how key rotation works.One subtlety worth noting: replay attack prevention. Every authentication JWT must carry a
jti(JWT ID) claim. The server must trackjtivalues and reject any that have been seen before within the token's allowed lifetime. Without this check, a stolen JWT could be used to impersonate the client.Designing the Rust Traits
Following the same philosophy as the persistence layer - decompose the specification into cohesive concerns, express each as a focused trait, and compose them - here is how authentication and authorization can be modeled in Rust.
The Principal: Who Is Making This Request?
Before we can make access control decisions, we need to know who is asking. In the backend services context, every request comes from a registered client, not a human user. We model the authenticated identity as a
Principal:Scopes as a First-Class Type
Rather than passing scope strings around and doing string matching everywhere, we model the SMART scope syntax as a structured type:
Token Validation: The AuthProvider Trait
Token validation is where authentication and authorization meet. A client presents a Bearer token; we need to turn that into a
Principal. TheAuthProvidertrait abstracts over how this happens - whether we're validating a JWT locally against the authorization server's published JWKS, or calling a token introspection endpoint:Two concrete implementations cover the two standard ways of validating tokens issued by an external authorization server:
Replay Attack Prevention
The
jti(JWT ID) check is subtle but important. A client generates a one-time-use JWT to authenticate, and the server must ensure that JWT cannot be replayed. The cache must be scoped to(iss, jti)pairs, not justjtivalues, to prevent one client from polluting the namespace of another:No ClientRegistry in HFS
A natural question at this point: where does HFS store client registrations? The answer is that it doesn't. Client registrations - the
client_id, the associated JWKS URL, the authorized scopes - all of that lives in the external authorization server. When an OAuth server registers a backend client, it manages that record. HFS has no opinion on it.This is a deliberate boundary. If HFS maintained a parallel registry of clients, you would immediately have two sources of truth to keep in sync, two places where a revoked client might still appear active, and two administration surfaces to secure. The operational overhead compounds quickly, especially across the number of client applications a high-volume deployment typically accumulates.
What HFS does need to know is whether a given token is currently valid and what scopes it carries. The
AuthProvidertrait handles this - either by verifying the token's signature against the authorization server's published JWKS, or by calling the introspection endpoint. Both paths produce aPrincipalthat HFS can reason about. The source of truth for the underlying authorization decision remains with the authorization server throughout.Access Control: The AuthorizationPolicy Trait
Authentication establishes who is making a request. Authorization answers what they're allowed to do. While scope-based checks cover the SMART specification requirements, real deployments often need additional policy - row-level security, compartment-based restrictions, tenant isolation enforcement. We separate this concern into its own trait:
A scope-based policy is the default and covers all SMART-compliant behavior:
Policies can be composed without coupling them together:
The Discovery Endpoint
The
/.well-known/smart-configurationdocument is how clients learn about the server's capabilities. We model its content as a typed struct that can serialize to JSON:Putting It Together: The Auth Layer as a Middleware
The auth layer sits between the network and the FHIR request handlers. Every incoming request passes through it. The
RequestContextproduced here is the single source of truth for the rest of the system about who is making this request:Identity Provider Integration: How Real-World Services Plug In
The architectural decision to keep HFS out of the authorization server business is not merely a theoretical preference - it is what makes the system work with the identity providers that organizations already have deployed. Every major cloud identity platform and open-source authorization server speaks OAuth 2.0 and publishes JWKS endpoints. The
AuthProvidertrait is deliberately shaped to exploit this convergence.Here is how the most commonly encountered providers map onto the design.
Keycloak
Keycloak is the open-source option that many healthcare organizations gravitate toward because it can be self-hosted - an important consideration when data sovereignty requirements preclude cloud-hosted identity. It is also the most natural fit for SMART-on-FHIR because of its extensibility.
How it connects:
Keycloak exposes a JWKS endpoint at
{keycloak-url}/realms/{realm}/protocol/openid-connect/certs. TheJwksBearerAuthProviderpoints at this URL and validates tokens locally. Keycloak's token endpoint ({keycloak-url}/realms/{realm}/protocol/openid-connect/token) is the URL that appears in the/.well-known/smart-configurationdocument served by HFS.Scopes and client credentials:
Keycloak supports the
client_credentialsgrant type natively. Backend service clients are registered as "confidential" clients with a service account enabled. SMART v2 scopes (system/Patient.rs, etc.) are configured as client scopes in Keycloak - either as default scopes assigned at registration or as optional scopes that the client requests at token time.SMART-specific considerations:
Out of the box, Keycloak does not understand SMART scope syntax - it treats scopes as opaque strings. This is fine for HFS, which parses the scope string from the token itself via
ScopeSet::parse(). However, if you want Keycloak to enforce scope restrictions at the authorization server level (rejecting requests for scopes a client isn't authorized to hold), you need to configure Keycloak's client scope mappings to match the SMART scope vocabulary. Community extensions exist that add SMART-aware scope validation to Keycloak directly.Configuration sketch:
Okta
Okta is widely deployed in US healthcare, particularly among larger health systems and payer organizations. Its appeal is operational simplicity - it is a fully managed service with strong compliance certifications (SOC 2, HIPAA BAA available).
How it connects:
Okta publishes JWKS at
https://{okta-domain}/oauth2/{authorization-server-id}/v1/keys. For the default authorization server, the path ishttps://{okta-domain}/oauth2/default/v1/keys. TheJwksBearerAuthProviderfetches keys from this endpoint.Scopes and client credentials:
Backend services in Okta are configured as "Service" application types using the
client_credentialsgrant. Custom scopes are defined on the Okta authorization server - you create scopes matching the SMART syntax (system/Patient.rs,system/Observation.r, etc.) and assign them to the service application via an access policy.Token format:
Okta issues JWTs by default, which is ideal for the
JwksBearerAuthProviderpath. The tokens include standard claims (iss,sub,aud,exp,iat) plus ascpclaim containing the granted scopes as an array of strings. Note that Okta usesscp(an array) rather than the OAuth standardscope(a space-delimited string) - theScopeSet::parse()implementation should handle both formats.Configuration sketch:
Auth0
Auth0 (now part of Okta, Inc., but operated as a separate platform) is popular among digital health startups and smaller organizations building FHIR integrations. Its developer experience is polished, and it supports the
client_credentialsgrant out of the box.How it connects:
Auth0's JWKS endpoint is at
https://{tenant}.auth0.com/.well-known/jwks.json(orhttps://{custom-domain}/.well-known/jwks.jsonfor custom domains). TheJwksBearerAuthProviderworks without modification.Scopes and client credentials:
Machine-to-machine applications in Auth0 are authorized against an "API" (Auth0's term for a resource server). You define the API with an identifier (typically the FHIR server's base URL), create custom scopes matching SMART syntax, and authorize specific M2M applications to request those scopes.
Token format:
Auth0 issues RS256-signed JWTs by default. The
scopeclaim is a space-delimited string (standard OAuth format). Theaud(audience) claim contains the API identifier - HFS should validate this matches its own base URL to prevent token confusion attacks where a token issued for a different API is presented to HFS.Audience validation note:
This is worth calling out because it affects the
AuthProvidertrait. Auth0 tokens carry anaudclaim, and Auth0's documentation strongly recommends validating it. TheJwksBearerAuthProvidershould accept anexpected_audienceconfiguration alongsideexpected_issuer. This is good practice regardless of provider - it prevents a token issued by the same authorization server but intended for a different resource server from being accepted by HFS.Configuration sketch:
Microsoft Entra ID (Azure AD)
Microsoft Entra ID (formerly Azure Active Directory) is the dominant identity platform in enterprises using Azure. Many hospital systems running on Azure already have Entra ID deployed for workforce identity, making it a natural choice for FHIR API authorization as well. Microsoft also offers the Azure Health Data Services FHIR service, so organizations integrating HFS alongside Azure FHIR will often share the same Entra ID tenant.
How it connects:
Entra ID publishes JWKS at
https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys. TheJwksBearerAuthProviderpoints here. Note the tenant-specific URL - Entra ID is a multi-tenant platform, and you must configure HFS with your specific tenant's JWKS endpoint to avoid accepting tokens from other tenants.Scopes and client credentials:
Backend services are registered as "App registrations" in Entra ID. The
client_credentialsflow is supported natively. Scopes in Entra ID are defined as "Application permissions" (also called "app roles") on the target application registration. You define permissions matching SMART scope strings and grant them to the calling application via admin consent.Token format:
Entra ID v2.0 tokens are JWTs signed with RS256. The key claims are:
iss:https://login.microsoftonline.com/{tenant-id}/v2.0aud: the Application ID URI of the target app registrationroles: an array of granted application permissions (this is where SMART scopes appear)Important distinction: Entra ID places application permissions in a
rolesclaim, not ascopeorscpclaim. Thescopeclaim in a client_credentials token from Entra ID typically contains only a default scope (e.g.,https://fhir.example.org/.default). The actual fine-grained permissions are inroles. TheScopeSetparser needs to be aware of this provider-specific mapping - or, more cleanly, theJwksBearerAuthProvidershould be configurable with the claim name from which to extract scopes.Configuration sketch:
Google Cloud Identity (GCP)
Google Cloud's identity platform is relevant for organizations running healthcare workloads on GCP, and it integrates with Google's Healthcare API. GCP's approach to service-to-service authentication uses Google-signed JWTs and service accounts.
How it connects:
Google's JWKS endpoint is at
https://www.googleapis.com/oauth2/v3/certs. Service accounts authenticate via theclient_credentials-equivalent flow: the service account creates a self-signed JWT, exchanges it athttps://oauth2.googleapis.com/token, and receives a Google-signed access token. TheJwksBearerAuthProvidervalidates tokens against Google's published keys.Scopes and service accounts:
GCP uses service accounts for machine-to-machine authentication. Scopes in GCP are typically Google API scopes (
https://www.googleapis.com/auth/...), not SMART scopes. To map this to the SMART model, there are two approaches:client_idequivalent in an external scope mapping configuration. HFS (or a lightweight proxy) maps the authenticated service account identity to SMART scopes via a lookup.Token format:
Google-issued access tokens are opaque by default (not JWTs). However, Google-signed identity tokens (obtained by specifying a target audience) are JWTs and can be validated against Google's JWKS. For HFS integration, you want identity tokens, not access tokens.
Practical consideration:
GCP's identity model is the least natural fit for SMART Backend Services among the providers discussed here, because Google's scope system is Google-API-centric rather than application-defined. Organizations on GCP may find it cleaner to deploy Keycloak on GKE as their SMART authorization server and use GCP's identity platform for authenticating to Keycloak itself, rather than trying to make Google's token format directly express SMART scopes.
What This Means for the Trait Design
The provider survey above surfaces a few concrete requirements for the
JwksBearerAuthProvider:Configurable scope claim name: Okta uses
scp(array), Auth0 usesscope(string), Entra ID usesroles(array), and the OAuth standard saysscope(string). The provider must know which claim to read.Audience validation: Auth0 and Entra ID both require
audclaim validation. This should be a standard, not optional, part of token validation - even Keycloak and Okta benefit from it as a defense-in-depth measure.Multiple scope formats: Space-delimited strings and JSON arrays are both in use.
ScopeSet::parse()should handle both transparently.Issuer URL strictness: Trailing slashes matter. Auth0 issues tokens with
iss: "https://tenant.auth0.com/"(trailing slash), while Entra ID does not. Issuer comparison must be exact, but the configuration documentation should call this out.These are not esoteric edge cases - they are the first things you encounter when connecting a real identity provider. By making them explicit in the trait design, we avoid the situation where the
JwksBearerAuthProviderworks perfectly against a test JWKS endpoint but fails on the first real deployment.Here is the
JwksBearerAuthProviderstruct updated to reflect these requirements:AI Agents as Backend Service Clients
The same AI-driven forces mentioned in the introduction - clinical decision support engines, analytics pipelines, population health platforms - increasingly take the form of autonomous AI agents. These agents need programmatic access to FHIR APIs just like any other backend service. The good news is that the SMART Backend Services protocol and the
client-confidential-asymmetricauthentication profile are already designed for exactly this kind of machine-to-machine interaction. No new protocol is needed. What is needed is a clear picture of how an AI agent registers, authenticates, and operates within the existing framework.The Registration Flow for an AI Agent
AI agent registration follows the standard SMART client-confidential-asymmetric registration protocol. The process happens at the authorization server - not at HFS - and the key steps are:
1. Key pair generation
The AI agent (or the system that provisions it) generates an asymmetric key pair. SMART requires support for both
RS384(RSA with SHA-384) andES384(ECDSA with P-384). The private key stays with the agent; the public key is communicated to the authorization server.2. Public key registration
The agent's public key is registered with the authorization server as a JSON Web Key (JWK) within a JWK Set, per RFC 7517. There are two methods:
JWKS URL (preferred): The agent hosts its public keys at a TLS-protected URL accessible without authentication. The authorization server fetches keys from this URL on demand. This is the preferred method because it enables key rotation without re-registering - the agent publishes a new key at the same URL, and the authorization server picks it up on its next fetch (respecting
Cache-Controlheaders). For AI agents running in cloud environments, this URL is typically a well-known endpoint on the agent's own infrastructure.JWK Set directly (supported but discouraged): The agent provides its JWK Set at registration time. The authorization server stores the key material. This prevents in-band key rotation - if the agent needs to rotate keys, it must re-register. The spec recommends including a successor key alongside the active key to mitigate this limitation.
Each JWK must include a
kid(Key ID) that is unique within the agent's key set. For RSA keys, the required fields arekty,kid,n, ande. For ECDSA keys:kty,kid,crv,x, andy.3. Scope assignment
The administrator configures which SMART scopes the agent is authorized to request. For example, a clinical decision support agent might receive
system/Patient.rs system/Observation.rs system/Condition.rs- read and search access to the clinical data it needs for its reasoning, but no write access. A data integration agent synchronizing patient demographics might receivesystem/Patient.crud. The principle of least privilege applies: agents should receive only the scopes their function requires.4. Client ID issuance
The authorization server assigns the agent a
client_id. This is the identity that the agent will assert in its authentication JWTs.How the Agent Authenticates
Once registered, the agent authenticates using the standard
client_credentialsflow with a signed JWT assertion. The sequence is identical to any other backend service client:The agent constructs a JWT with the following claims:
iss: the agent'sclient_idsub: the agent'sclient_idaud: the authorization server's token endpoint URLexp: expiration time (maximum 5 minutes in the future)jti: a unique identifier for this JWT (for replay prevention)The JWT header includes:
alg:RS384orES384kid: the Key ID matching the registered public keytyp:JWTjku(optional): the URL of the agent's JWKS endpoint, if one was registeredThe agent signs the JWT with its private key and POSTs to the token endpoint:
The authorization server validates the JWT:
jkuheader (if present) matches the registration-time JWKS URLjkuor the registered JWKS URL) and matches bykidjtihas not been seen before within the allowed lifetime windowclient_idis known and matches the JWTissclaimIf validation succeeds, the authorization server issues a short-lived access token (recommended maximum lifetime: 5 minutes).
The agent presents this token as a Bearer token on every FHIR API request to HFS. HFS validates the token via its
AuthProvider(JWKS signature verification or token introspection) and enforces scope-based access control via itsAuthorizationPolicy.Provider-Specific Registration for AI Agents
Each identity provider has its own administrative interface for registering backend service clients. Here is how AI agent registration maps to the providers discussed earlier:
client_credentialsgrant. Add custom SMART scopes to the authorization server and assign them via an access policy. Upload the agent's public key (JWKS).docker compose upwith bundled realm exportKey Rotation for Long-Running Agents
AI agents that run continuously (monitoring services, real-time alerting, continuous data pipelines) will eventually need to rotate their cryptographic keys. The JWKS URL method makes this straightforward:
kid).Cache-Controlheaders), the agent removes the old public key from its JWKS.The authorization server and HFS require no reconfiguration. The key rotation is entirely self-service, which is important for environments with many AI agents that may be provisioned and managed programmatically.
Scope Governance for AI Agents
The composable
AuthorizationPolicytrait is particularly relevant for AI agent deployments. Beyond basic SMART scope enforcement, organizations may want to layer additional policies:system/Patient.rscould read every patient in the system. A rate-limiting policy can throttle request volume.system/Observation.rsmight be restricted to observations linked to patients within a specific care team or facility.These are implemented as
AuthorizationPolicytrait objects composed viaCompositeAuthorizationPolicy, exactly as described in the trait design section. The agent itself is unaware of these policies - it authenticates, receives a token, and makes FHIR requests. The policy enforcement is entirely server-side.The FHIR security specification gives careful guidance on how to respond when access is denied - and the guidance is deliberately conservative. Returning too much information about why access was denied can leak data:
The Helios FHIR Server will make these decisions via policy configuration - defaulting to 404 for most access denials, with the exact behavior configurable per deployment.
Audit Logging: Non-Negotiable
Every access control decision must be recorded. This is both a FHIR recommendation and a regulatory requirement in most jurisdictions (HIPAA's accounting of disclosures, for instance). Audit events record who accessed what, when, from where, and whether access was granted or denied.
The auth layer is responsible for emitting these events, and they flow to the
AuditStoragetrait described in the persistence layer discussion:What's Not in Scope (Yet)
This document focuses on Backend Services. Several related concerns are deferred:
Principalenum gains aUservariant and theAuthProvidertrait gains OAuth PKCE code exchange support.Proposed Next Steps
The traits sketched above are a starting point. To move toward implementation:
JwksBearerAuthProvider(signature verification against the authorization server's JWKS) andIntrospectionAuthProvider(round-trip to the introspection endpoint), and let operators choose. JWKS-based validation is the recommended default for high-throughput deployments since it avoids a network call on every request. Introspection is the fallback for authorization servers that issue opaque tokens or where immediate revocation semantics are required.Cache-Controlheaders from JWKS endpoints. We need an HTTP client with correct cache semantics here, and a cache-busting path for key rotation events.client_idandTenantContextmap to each other? One client per tenant? Multiple clients per tenant? This affects theTenantResolverdesign.jticache backend: An in-memory LRU cache is sufficient for single-instance deployments. Distributed deployments need a shared cache (Redis is the obvious choice) so that a replayed client-assertion JWT cannot slip through on a different HFS instance.scp,scope,roles) described in the identity provider section.Closing Thoughts
Authentication and authorization in healthcare interoperability are load-bearing infrastructure. They protect patient data, enable regulatory compliance, and establish the trust relationships that make clinical AI workloads possible. Getting the design right matters - and getting it testable and composable matters almost as much.
The Rust trait system is a natural fit for this problem. The compiler enforces that every code path produces a
RequestContext, that no storage operation happens without aTenantContext, and that new auth providers or policy implementations must satisfy the same contracts as the ones they replace. These guarantees hold regardless of how complex the deployment becomes.Thank you for reading. I look forward to the discussion.
Beta Was this translation helpful? Give feedback.
All reactions