Multi-Tenant Access Control for Shared Healthcare Infrastructure with Fire Arrow
A practical reference architecture for multi-tenant authorization on a shared FHIR server. Covers organization-based tenant isolation, role differentiation within tenants, cross-organizational CareTeam access, server-side search narrowing, deny-by-default enforcement, and a concrete multi-clinic operating example.
Executive Summary
Multi-tenancy sounds straightforward: one platform, multiple customers, no cross-tenant visibility. Healthcare products almost never stop there. A clinic may need doctors, nurses, and IT staff to see different slices of the same system. A support team may need temporary access across customers. A specialist may need visibility into one patient from another organization without seeing that organization’s other data.
Multi-tenancy in healthcare is an authorization design problem, not just an infrastructure decision.
Many backend teams solve this by combining database-level row filters with custom API authorization code. The policy starts in one place and spreads: some rules live in the database, some in REST endpoints, some in GraphQL resolvers, and more accumulate as product requirements change. Over time, keeping every access path aligned becomes the dominant cost.
The central design principle of this paper is: tenant boundaries should be expressed as data in the same model that describes the clinical and organizational domain, not as custom code layered on top.
Fire Arrow Server uses the FHIR layer itself as the permission core. Tenant boundaries, user roles, and clinical relationships are represented as FHIR resources. Authorization rules connect those relationships to reusable validators applied consistently at request time across REST, GraphQL, and other access paths.
This paper walks through that model. It covers the authorization pipeline, organization-based isolation with the LegitimateInterest validator, cross-organizational access with CareTeam, rule structure and configuration, search narrowing, deny-by-default behavior, and a concrete operating example with multiple clinics and roles.
The goal is not to claim that multi-tenancy becomes trivial. The goal is to show how the permission model can stay close to the domain and remain explicit as the product grows.
1. Audience and Scope
This paper is intended for:
- CTOs, CIOs, and technical product leaders evaluating multi-tenant healthcare architectures.
- Solution and platform architects responsible for authorization design in shared-infrastructure deployments.
- Security and compliance stakeholders reviewing how tenant isolation is enforced across REST and GraphQL.
- Engineering teams building healthcare SaaS platforms, multi-site provider networks, or shared research infrastructure on top of FHIR.
In scope
This paper covers:
- tenant isolation on a shared FHIR server using organization-based authorization,
- role differentiation within tenants using PractitionerRole codes,
- cross-organizational access for care coordination using CareTeam,
- authorization pipeline behavior, rule structure, and configuration,
- search narrowing for REST and GraphQL,
- deny-by-default enforcement,
- a concrete operating example with multiple clinics, roles, and access tiers.
Out of scope
This paper does not:
- provide legal advice or make blanket regulatory compliance claims,
- prescribe infrastructure-level isolation strategies (separate databases, separate deployments),
- address multi-region data residency or partitioning,
- cover network-level security controls.
2. The Problem
There is real demand for shared healthcare infrastructure:
- multi-clinic SaaS platforms where each clinic manages its own patients,
- hospital networks with departmental hierarchies,
- research programs that need site isolation with coordinator visibility,
- care coordination products that combine staff, patient, caregiver, and support access models.
A typical SaaS application represents a tenant with an organization record and a tenant identifier on related rows. Simple tenant-ID filtering works when every user inside the tenant sees the same data and cross-tenant access is never allowed.
Healthcare products are usually more complex.
2.1 Tenant boundaries are not simple ownership checks
A single shared server may need to support multiple clinics that remain isolated from each other, different internal roles within each clinic, platform support staff who can assist any clinic when invited, and external specialists who need access to one patient without seeing the rest of a clinic’s data. The question is not just “which tenant owns this record” but “which authenticated actor may access this resource, through which relationship, and for which operation.”
2.2 Cross-tenant access is a product requirement
Specialists consult on patients from other organizations. Support teams need scoped operational access across all tenants. Research coordinators need cross-site visibility. These are not edge cases. They are features that product teams will need early. A multi-tenancy model that cannot express cross-tenant exceptions cleanly will accumulate workarounds.
2.3 Role differentiation within a tenant is not optional
Even inside a single clinic, a doctor, a nurse, an IT administrator, and a patient should not see the same data. Doctors may need full clinical read and write access. Nurses may need read-only clinical access. IT administrators may need to manage devices and practitioner accounts without touching patient data. Patients need access to their own records and their organization’s practitioner directory. A tenant boundary alone does not express any of this.
2.4 Multiple API surfaces multiply the enforcement burden
A healthcare backend typically exposes direct reads, filtered searches, GraphQL queries, includes and reverse includes, subscriptions, file access, and workflow-driven operations. If authorization is implemented separately for each surface, keeping them aligned becomes the long-term problem.
2.5 Tenant relationships change at runtime
Healthcare data relationships are not static. Patients transfer between clinics. Practitioners join and leave organizations. Roles are activated and deactivated. New clinics are onboarded. These changes should take effect immediately in the authorization model, not require data migrations, infrastructure provisioning, or code deployments.
In systems that use infrastructure-level partitioning (separate databases, separate FHIR stores, or URL-based routing per tenant), a patient transfer means moving data between partitions. Every resource referencing that patient must be relocated, and references must be rewritten or proxied. Adding a new tenant may require provisioning a new database or store instance.
In data-level models, a patient transfer is an update to one field (managingOrganization). A new clinic is a new Organization resource. A role deactivation is a status change on a PractitionerRole. The authorization model should react to these changes without requiring coordination across infrastructure or application layers.
2.6 Caregiver and device access run alongside staff access
Healthcare products often need access models beyond staff and patients. A parent may need to view a child’s clinical data through a caregiver app. An IoT device may need to write observations scoped to its owning organization. A peer support mentor may need limited visibility into another patient’s care plan. These access patterns run in parallel with staff and patient access and must be expressed in the same authorization model.
3. Why Conventional Backend Approaches Become Brittle
3.1 Database row-level security is helpful but incomplete
Row-level security is close to the data and can prevent accidental overexposure at the query layer. But in healthcare, permissions depend on more than ownership: a practitioner’s role inside an organization, a patient’s managing organization, a caregiver relationship, membership in a care team, hierarchical organizational inheritance, and differences between read, search, create, update, subscribe, and export operations. Those concepts are difficult to express cleanly as low-level database predicates, especially when the application exposes multiple interfaces on top.
3.2 Custom API authorization logic spreads and hardens
The alternative is to keep the database permissive and implement authorization in application code: add tenant checks in controllers, services, or resolvers.
The difficulty appears later. Product requirements change. New resource types are added. Search endpoints become more complex. GraphQL is introduced beside REST. A workflow step needs an exception. A support role is added. A one-off sharing case becomes a permanent product feature. Authorization logic is no longer a single policy. It is a growing collection of conditional statements distributed across the codebase.
The lifecycle of custom authorization code in a healthcare backend follows a pattern. The first version handles basic tenant-ID filtering on direct reads. The team then adds the same filtering to search endpoints, often forgetting one or two parameters that allow unfiltered results. GraphQL is introduced, and each resolver needs its own tenant check. Over time, the REST and GraphQL implementations diverge. A bug is found where a search endpoint returns cross-tenant results. The fix is applied to REST but not to GraphQL, or to one resource type but not another. A cross-tenant specialist consultation feature is requested. The team adds a special case, then another, then a third. Each special case is a new conditional branch in a different part of the codebase.
After twelve months, the team is maintaining tenant logic in dozens of files. A new developer cannot determine the effective access policy by reading any single artifact. The policy is the emergent behavior of scattered code, and changing it safely requires understanding all of it.
3.3 Searches are a distinct and underprotected attack surface
A system can appear secure at the resource-read level while remaining open through search. A missing tenant filter on a search endpoint does not produce an error; it produces results from all tenants. During development, this often goes undetected because test environments have only one tenant. In production, the same unfiltered search exposes cross-tenant data silently.
Search-based data inference is a separate concern. Even when search results are filtered correctly, the ability to search on certain parameters can reveal information. A system that allows searching observations by value across tenants leaks clinical data through the search interface itself, even if the response is properly scoped. Protecting against this requires blocking specific search parameters per role, not just filtering the results.
3.4 Access control errors have asymmetric consequences
A missing filter, an unguarded search parameter, or a resolver that bypasses a shared check can expose more data than intended. Broken access control has been ranked as the top web application security risk by OWASP, covering a wide range of failures involving unauthorized data exposure and privilege misuse.
The burden grows quickly. Every new product path becomes another place where policy must stay aligned.
3.5 Infrastructure-level partitioning trades flexibility for isolation
Some FHIR server products and cloud services address multi-tenancy by putting each tenant in a separate database, a separate FHIR store, or a separate URL namespace. Isolation is strong, but the model is rigid.
A practitioner who works at two clinics needs two accounts or a cross-partition proxy. A patient who transfers between organizations requires data migration, not a field update. Adding a new tenant means provisioning infrastructure, not creating a resource. A cross-organizational specialist consultation requires either a cross-partition reference (which many systems do not support granularly) or a data copy.
These trade-offs are acceptable in some deployments. But for products that need flexible organizational structures, cross-tenant care coordination, or dynamic membership changes, infrastructure-level partitioning pushes authorization complexity into the application layer anyway.
3.6 Tag-based and label-based isolation has gaps
Some systems use security labels or metadata tags on FHIR resources to mark tenant ownership. The server filters responses by matching the request’s tenant context against the resource’s label. The approach is conceptually simple but has practical limitations.
Labels must be applied before data is loaded. Retrofitting tenant labels to an existing dataset requires a migration. In some implementations, instance reads by ID bypass label filtering entirely: GET /fhir/Patient/abc-123 returns the resource regardless of its label, because the server treats direct ID access as authoritative. Post-fetch verification is required to close this gap, and the application must implement it.
Label-based filtering also has no inherent connection to clinical relationships. The label says “this resource belongs to Tenant A.” It does not say “this practitioner has a cardiology role in Tenant A but not a radiology role in Tenant B.” Organizational hierarchy, role differentiation, and cross-tenant care coordination must still be built as application logic on top of the label mechanism.
3.7 SMART on FHIR scopes do not address multi-tenancy
SMART on FHIR is an OAuth 2.0 profile that standardizes how applications obtain authorized access to a FHIR server. It defines two authorization patterns: App Launch, where a user-facing application connects with context from an EHR session or user selection, and Backend Services, where a headless client connects with pre-authorized scopes and no user involvement. It is widely adopted and mandated by US regulation (ONC HTI-1) for certified health IT.
SMART scopes use a syntax tied to FHIR resource types and operations. A scope like patient/Observation.rs grants read and search access to Observations for a specific patient. Version 2.0 added granular scopes with search parameter constraints (user/Observation.rs?category=laboratory restricts access to laboratory observations). Scopes fall into three categories: patient/ scopes are bound to a single patient context, user/ scopes are bound to the authorizing user’s permissions, and system/ scopes apply to a pre-configured data set for backend services.
The specification is explicit about what it delegates. The scopes documentation states: “Neither SMART on FHIR nor the FHIR Core specification provide a way to model the ‘underlying’ permissions at play here; this is a lower-level responsibility in the access control stack.” Multi-tenancy, organizational isolation, role differentiation, and cross-tenant exceptions all fall into that lower layer. A user/Observation.rs scope says the token holder can read and search Observations, but it does not say which organization’s Observations, which hierarchy level, or whether a CareTeam membership should grant an exception for a specific patient at another organization.
The architecture also requires coordination between components. The authorization server must understand FHIR-specific scope syntax and serve a .well-known/smart-configuration discovery document. Client applications must support SMART-specific OAuth flows and scope negotiation. (SMART’s Token Introspection API reduces some of this coupling between the resource server and the authorization server, but the scope vocabulary itself remains FHIR-specific.) This coordination serves a clear purpose: it standardizes how apps are authorized across different EHR systems. It does not, however, standardize how the FHIR server decides which data a given identity is allowed to reach within a shared, multi-tenant data set.
For multi-tenant authorization on a shared FHIR server, the decisions that SMART scopes do not express include: which organizations does this practitioner belong to, what role do they hold in each, does their organizational hierarchy grant inherited access, are they on a CareTeam for a patient at another organization, and should search results be narrowed to authorized data before the response is assembled. These decisions require knowledge of the FHIR data model itself: Organization, PractitionerRole, CareTeam, Patient.managingOrganization, and their relationships.
Fire Arrow’s authorization model operates at the layer SMART delegates to implementations. It accepts standard OAuth 2.0 access tokens without requiring the authorization server to understand FHIR resource types or issue FHIR-specific scopes. The token is mapped to a FHIR identity resource, and access is evaluated based on the relationships between resources in the database. The two approaches address different parts of the authorization stack: SMART standardizes how a client obtains and presents a token; Fire Arrow’s validators determine what data that token’s identity can reach.
4. Design Principles
4.1 Express tenant boundaries as domain data
Use the same FHIR resources that describe organizational structure and clinical relationships to describe who may access what. Do not invent a parallel permission system.
4.2 Enforce authorization centrally, at request time
The server should be the single enforcement point. Client applications and API consumers should not carry the responsibility of applying the correct tenant filter.
4.3 Start from deny by default
If no rule matches a request, the result should be denial. Policy should be an explicit allow-list, not a loose collection of exceptions.
4.4 Keep rules declarative
Authorization rules should describe which role may perform which operation on which resource type, using which validator. Adding a new role or resource type should be a configuration change, not a code change.
4.5 Treat cross-tenant access as a clinical relationship
When a specialist needs access to one patient across organizational boundaries, express that as a CareTeam membership, not as a tenant-wide exception.
4.6 Apply search narrowing server-side
The server should narrow search results to authorized data before returning them. Callers should not have to remember to include the correct tenant filter on every query.
4.7 Keep rule evaluation additive
A practitioner should not lose organization-wide access because they are also on a CareTeam, and vice versa. The effective access for any request should be the union of all matching rules.
5. Fire Arrow’s Authorization Model
Fire Arrow uses the FHIR model itself as the place where identity, organizational structure, and access relationships are expressed.
The key FHIR resources are:
- Patient and Practitioner as authenticated identities,
- PractitionerRole for assigning practitioners to organizations with role codes,
- Organization for tenant and hierarchy modeling (using
Organization.partOffor parent-child relationships), - Patient.managingOrganization for patient-to-tenant assignment,
- CareTeam for patient-specific cross-organizational access.
Authorization rules map four required fields into a single policy entry:
- a client role (Patient, Practitioner, RelatedPerson, Device),
- a FHIR resource type (Patient, Observation, Condition, etc.),
- an operation (read, search, create, update, subscribe, graphql-read, graphql-search, etc.),
- and a validator (LegitimateInterest, CareTeam, PatientCompartment, Forbidden, Allowed, etc.).
Rules can also carry optional constraints: practitioner-role-system and practitioner-role-code to restrict a rule to practitioners holding a specific role, care-team-role to filter by participant role within a CareTeam, identity-filter for FHIRPath-based conditions on the client identity, property-filter for field-level response redaction, and blocked-search-params or blocked-includes to prevent specific search vectors.
The default validator is Forbidden. If no rule matches a given request, the server denies it. The result is an explicit allow-list.
6. How the Authorization Pipeline Works
Fire Arrow uses a rule-based authorization pipeline. Each incoming request passes through these stages:
- Authenticate the client using a JWT (OAuth 2.0 / OIDC) or API token.
- Resolve identity by mapping the token to a FHIR resource (Patient, Practitioner, RelatedPerson, or Device) via identifier lookup, email fallback, or optional auto-create.
- Build the applicable rule set by matching the client’s role, the requested resource type, and the operation against all configured rules. All matching rules contribute to the effective access (additive evaluation). After rule matching, the default validator’s rules are always appended at the end.
- Check blocked parameters for search requests where a rule disallows specific search paths or includes.
- Narrow the search so that only authorized resources can appear in results.
- Execute and validate the request using the configured validators.
Two aspects of this pipeline matter especially for multi-tenancy.
First, server-side search narrowing is part of core authorization behavior, not an optional add-on. On REST, Fire Arrow appends search constraints derived from the matching validators. On GraphQL, it builds alternative search parameter maps with OR semantics: each alternative is executed independently, and results are merged and deduplicated by resource ID. In many systems, reads are protected more carefully than searches because direct reads are visible in endpoint code while searches are assembled dynamically. Fire Arrow treats search narrowing as a first-class authorization concern for both REST and GraphQL.
Second, GraphQL operations use separate operation names prefixed with graphql- (for example, graphql-read and graphql-search). Authorization rules can define different access policies for REST and GraphQL independently.
If no matching rule grants access, the Forbidden default validator produces an impossible search constraint (on search paths) or a deny rule (on read and write paths), ensuring zero results or a 403 response.
7. Reusable Validators
The rule system is paired with a catalog of validators that represent recurring healthcare access patterns.
7.1 LegitimateInterest
LegitimateInterest implements organization-based access control. It is the core of Fire Arrow’s multi-tenancy model.
For practitioner clients, the validator resolves the practitioner’s active PractitionerRole resources, collects the referenced organizations, and grants access to patients whose managingOrganization points to one of those organizations. Clinical data (Observations, Conditions, Encounters, MedicationRequests, etc.) that belongs to those patients is also accessible. Organizational resources linked to those organizations (Devices, Locations, HealthcareServices, InsurancePlans) are included as well.
A practitioner who works at two clinics has two active PractitionerRoles and sees both clinics’ patients. No duplicate accounts or cross-partition proxies are required. The organizational scope is computed from the PractitionerRole data at request time.
For patient clients, LegitimateInterest works in the other direction. A patient registered at City General Hospital can browse the hospital’s practitioner directory, see available locations and healthcare services, and view devices owned by the hospital. The patient cannot see practitioners, services, or data belonging to a different hospital. The access boundary follows Patient.managingOrganization.
Role filtering: When authorization rules include practitioner-role-system and practitioner-role-code, only PractitionerRoles with a matching code contribute to the organizational scope. A rule targeting the doctor code evaluates against a different set of organizations than a rule targeting the ict code, even for the same practitioner. Role differentiation within a single tenant needs no separate configurations. IT administrators who need to manage devices and practitioner accounts but should never see patient data simply have no rules for clinical resource types; those requests fall through to the Forbidden default.
Organization hierarchies: Organizations can form hierarchies using Organization.partOf. The configuration property fire-arrow.validators.legitimate-interest.role-inheritance-levels controls how many levels downward a practitioner’s access cascades. With the default of 0, a practitioner only sees data in organizations where they have a direct PractitionerRole. With role-inheritance-levels: 2, a practitioner with a PractitionerRole at a parent organization also sees data two levels down through the hierarchy. Inheritance is strictly downward: child organization members never gain access to parent data.
The hierarchy model covers hospital networks (Regional Health Authority sees all departments), SaaS vendor support teams (support staff at the root organization see all tenants), and research consortiums (a study coordinator at the parent organization sees all participating sites).
7.2 CareTeam
Some access is patient-specific and crosses organizational boundaries. A specialist at one clinic may need access to a single patient at another clinic as part of coordinated care. A home care nurse from an external agency may need to see a discharged patient’s medication list. A multidisciplinary diabetes team may include a primary care physician, an endocrinologist from a different clinic, a dietician, and a diabetes nurse educator, each at a different organization. Modeling any of these as broad tenant exceptions opens access to an entire organization when only one patient is involved. Modeling them as CareTeam memberships scopes the exception to a specific patient.
The CareTeam validator grants access based on FHIR CareTeam membership. A practitioner can be a CareTeam member through three paths:
- Direct membership: the Practitioner resource is listed as a participant.
- Via PractitionerRole: one of the practitioner’s PractitionerRole resources is listed.
- Via Organization: an organization where the practitioner holds a role is listed, giving all practitioners in that organization access to the CareTeam’s patient.
All three paths are resolved and combined. CareTeam membership is patient-scoped: adding a practitioner to a patient’s CareTeam grants access to that patient’s data without granting access to other patients at that patient’s organization.
Because rule evaluation is additive, a practitioner who has LegitimateInterest access to their own organization and CareTeam access to a patient elsewhere retains both scopes simultaneously. Neither overrides the other.
Nested CareTeams are supported. A CareTeam can list another CareTeam as a participant, and resolution follows the hierarchy up to a configurable depth (fire-arrow.validators.care-team.max-recursion-depth, default 5, maximum 10). Cycle detection prevents infinite loops.
Rules can include a care-team-role constraint to restrict access to participants holding a specific role within the CareTeam. Role matching uses the SNOMED CT coding system. A care coordination platform can grant different access tiers based on the member’s role: primary care providers see full clinical data while care coordinators see only care plans and tasks.
7.3 Compartment validators and other access models
The validator catalog includes additional validators for access models that do not follow organizational boundaries:
- PatientCompartment for patient-facing applications. A patient sees their own lab results, medications, appointments, and care plans. A caregiver app authenticating as a
RelatedPersoncan use RelatedPersonCompartment to access a child’s data with a more restricted view than the patient would have. - PractitionerCompartment for practitioner-linked resources (resources the practitioner authored or is responsible for).
- DeviceCompartment for IoT and device-scoped access. A monitoring device submits observations and reads its own configuration without accessing patient data beyond its compartment.
- Allowed for explicitly unconditional access where appropriate (for example, a practitioner reading their own identity via
$me, or all users accessing a shared terminology library). - Forbidden for deny-by-default enforcement.
These validators compose with LegitimateInterest and CareTeam through the additive rule model. A patient’s effective access may combine PatientCompartment rules (own clinical data) with LegitimateInterest rules (organization directory) in the same request. A RelatedPerson may have CareTeam access to one patient and RelatedPersonCompartment access to another. The server resolves the union of all matching rules at request time.
8. A Concrete Example: One Server, Two Clinics, Several Roles
Consider a healthcare SaaS platform hosting two independent clinics on a single Fire Arrow Server instance. The organization hierarchy has a root platform organization, with each clinic as a child via Organization.partOf.
Inside each clinic, access differs by role:
- Doctors need broad clinical read and write access to Patient, Observation, Condition, MedicationRequest, CarePlan, Encounter, Task, and DocumentReference within their organization.
- Nurses need read-only clinical access to the same resource types.
- IT administrators need access to organizational resources (Practitioner, PractitionerRole, Organization, Device, Location) but no access to patient data.
- Platform support staff hold a PractitionerRole at the root organization and gain access to all clinics through role inheritance.
- Patients can read their own clinical data (via PatientCompartment) and browse their organization’s practitioner directory (via LegitimateInterest).
- External specialists gain access to specific patients across clinic boundaries through CareTeam membership.
The authorization configuration uses Forbidden as the default validator. Doctor rules use LegitimateInterest with practitioner-role-code: doctor for read, search, create, and update operations on clinical resource types. Nurse rules use LegitimateInterest with practitioner-role-code: nurse for read and search only. IT administrator rules use LegitimateInterest with practitioner-role-code: ict for organizational resources only. No clinical resource rules exist for the ict code, so any attempt to access patient data falls through to Forbidden.
Cross-clinic specialist access is handled by a separate set of CareTeam rules without role-code restrictions. When Dr. Lee at Clinic B is added to a CareTeam for a patient at Clinic A, the CareTeam validator grants access to that patient’s data. Dr. Lee still retains full access to all Clinic B patients through LegitimateInterest. Both scopes are combined.
Role inheritance at role-inheritance-levels: 2 allows the platform support team (with PractitionerRoles at the root organization) to see data across both clinics. Clinic staff, who hold roles at the clinic level, never gain access upward to the root organization’s data.
The FHIR data model itself carries the relationships that drive this entire access model. Patients are assigned to clinics through managingOrganization. Staff are linked to organizations and roles through PractitionerRole. Cross-organizational care coordination is expressed through CareTeam. No separate tenant-permission layer exists alongside the clinical data model.
What happens when relationships change
The authorization model reacts to data changes at runtime, without code deployments or infrastructure changes:
- Patient transfer between clinics. Updating a patient’s
managingOrganizationfrom Clinic A to Clinic B immediately changes which practitioners can see that patient. Clinic A doctors lose access; Clinic B doctors gain it. The patient’s own access to their clinical data (via PatientCompartment) is unaffected; their view of organizational resources (practitioner directory, locations) shifts to the new organization. - Role deactivation. Setting a PractitionerRole’s
activeflag to false immediately revokes access through that role. If a doctor leaves Clinic A, deactivating their PractitionerRole removes their access to Clinic A patients on the next request. No manual cleanup of per-endpoint permissions is needed. - New clinic onboarding. Adding a new clinic means creating an Organization resource with
partOfpointing to the platform root. No server restart, no new database, no infrastructure provisioning. The existing rules apply to the new organization automatically because they are defined by role and validator, not by tenant identity. - Specialist consultation starts and ends. Creating a CareTeam for a patient with the specialist as a participant grants cross-clinic access. Removing the participant or deactivating the CareTeam revokes it. The access is scoped to that patient only.
9. Operational Considerations
9.1 One policy model instead of many fragments
When permissions are declared in one authorization model, changes are easier to reason about:
- adding a new role does not require auditing every resolver,
- extending access to a new resource type reuses the same validator pattern,
- introducing GraphQL does not require a separate policy layer,
- adding a support workflow or referral path does not require opening a tenant-wide exception.
When authorization is scattered across infrastructure and custom application code, every new feature that touches data visibility must also answer: which endpoints does this affect, do REST and GraphQL behave the same way, are search and direct reads aligned, does the support team need a special-case override?
A declarative permission model reduces that coordination burden.
9.2 Reviewability
Authorization models grounded in explicit FHIR relationships are easier for architects, compliance leads, and engineering leads to inspect than conditional statements spread across a codebase. The review conversation shifts from “did we protect endpoint X” to “is this the right relationship and validator for this role and operation.”
9.3 Identity filters and property filters
For finer-grained control, rules can include an identity-filter (a FHIRPath expression evaluated against the authenticated identity resource) to narrow which users within a role are affected. property-filter redacts or randomizes selected response fields after authorization succeeds. blocked-search-params and blocked-includes prevent specific search vectors from being used.
These features need to be used deliberately. Response-time redaction does not automatically close search-based side channels.
9.4 Caching
Fire Arrow uses a multi-layer cache for authorization lookups: identity resolution, practitioner organization memberships, organization hierarchy, and per-organization resource enumerations. Cache entries are automatically invalidated when relevant resources change (for example, creating a PractitionerRole invalidates the practitioner’s cached membership and the organization’s cached practitioner list). The first request after a cache miss is slower; subsequent requests benefit from cached results.
9.5 Debug mode and near-miss analysis
When a request is unexpectedly denied, the X-Fire-Arrow-Debug header shows the full rule evaluation trace: every rule that was evaluated and why it matched or did not.
The debug output also includes near-miss analysis. Instead of returning an opaque 403, the server generates hints about why the request was denied: the practitioner has no PractitionerRole in the relevant organization, the patient has no managingOrganization set, a rule exists for search but not for read on the same resource type, or the practitioner’s role code does not match any configured rule. These hints turn authorization troubleshooting from reading scattered application logs into reading a single structured response.
Debug mode should not be enabled in production because the output exposes the full authorization configuration.
9.6 Cache invalidation correctness
Authorization caches are automatically invalidated when the underlying data changes. Deactivating a PractitionerRole invalidates the practitioner’s cached organization membership and the organization’s cached practitioner list. Changing a patient’s managingOrganization invalidates the cached patient set for both the old and new organizations. Reparenting an organization (changing Organization.partOf) invalidates the hierarchy cache.
Cache invalidation is deferred until after the database transaction commits, preventing stale reads from concurrent requests. The effect is that authorization changes (role activation, patient transfers, hierarchy restructuring) take effect on the next request without manual cache clearing or service restarts.
10. Where This Approach Fits
Common deployment scenarios include:
- multi-clinic or multi-site healthcare SaaS platforms,
- hospital groups with departmental hierarchies,
- research platforms with site isolation and coordinator access,
- care coordination products that need selective cross-organizational sharing,
- digital health products that combine staff, patient, caregiver, and support access models.
Separate deployments per customer remain the right choice in some environments for contractual, regulatory, or operational reasons. Products that want shared infrastructure can use this model without reducing their authorization to simple tenant-ID filtering.
11. Implementation Path
11.1 Model organizations and identities in FHIR
Represent the tenant structure through Organization, Practitioner, PractitionerRole, and Patient. Use Organization.partOf for hierarchies. Use Patient.managingOrganization to assign patients to organizations.
11.2 Map authenticated users to FHIR identities
Connect bearer tokens or API tokens to FHIR identity resources so authorization decisions are made against domain entities. Fire Arrow supports JWT-based authentication (OAuth 2.0 / OIDC) and API tokens, with identity resolution via identifier lookup, email fallback, and optional auto-create.
11.3 Start from deny by default
Set fire-arrow.authorization.default-validator to Forbidden. No operation is implicitly allowed.
11.4 Add rules by role, resource, and operation
Define which roles may perform which operations on which resources. Use practitioner-role-system and practitioner-role-code to differentiate access tiers within the same client role. Attach the appropriate validator to each rule.
11.5 Handle exceptions as explicit relationships
When a specialist needs access to one patient from another organization, use CareTeam rather than widening a tenant boundary. When organizational access is appropriate, use LegitimateInterest rather than duplicating tenant checks across the codebase.
11.6 Test common and edge paths
Verify not only straightforward read operations but also searches, GraphQL queries, administrative flows, and cross-tenant access paths. Use the debug header (X-Fire-Arrow-Debug) in development to inspect rule evaluation.
12. Conclusion
Multi-tenancy in healthcare is often presented as an infrastructure question: shared server or separate deployment, shared database or isolated schema.
Infrastructure choices matter, but they are not the whole story. The harder problem is authorization: how to express tenant boundaries, internal roles, and justified exceptions in a way that remains understandable as the product grows.
Fire Arrow addresses this by using the FHIR layer as the permission core. Identity, organization, and care relationships are modeled as data. Access rules are declared explicitly. Reusable validators apply those rules consistently across REST, GraphQL, and other request types. Search narrowing is handled server-side. Deny-by-default is the starting point. Rule evaluation is additive, so organizational access and care-team access compose without overriding each other. When the underlying data changes (a patient transfers, a role is deactivated, a new clinic is created), the authorization model reacts without code changes or infrastructure coordination.
For teams building healthcare platforms with real multi-tenant requirements, the shift is from coding permissions around the data to expressing permissions in the data model that already defines the system.
References
-
Fire Arrow Docs: Multi-Tenancy with Fine-Grained Access Control. https://docs.firearrow.io/docs/server/how-to/multi-tenancy-access-control
-
Fire Arrow Docs: Authorization Concepts. https://docs.firearrow.io/docs/server/authorization/concepts
-
Fire Arrow Docs: LegitimateInterest Validator. https://docs.firearrow.io/docs/server/authorization/validators/legitimate-interest
-
Fire Arrow Docs: CareTeam Validator. https://docs.firearrow.io/docs/server/authorization/validators/care-team
-
Fire Arrow Docs: Identity Filters. https://docs.firearrow.io/docs/server/authorization/identity-filters
-
Fire Arrow Docs: Property Filters. https://docs.firearrow.io/docs/server/authorization/property-filters
-
Fire Arrow Docs: Authentication Overview. https://docs.firearrow.io/docs/server/authentication/overview
-
Fire Arrow Docs: Configuration Reference. https://docs.firearrow.io/docs/server/configuration
-
HL7 SMART App Launch Implementation Guide (v2.2.0, STU 2.2). https://hl7.org/fhir/smart-app-launch/
-
HL7 SMART App Launch: Scopes and Launch Context. https://hl7.org/fhir/smart-app-launch/scopes-and-launch-context.html
-
HL7 SMART App Launch: Backend Services. https://hl7.org/fhir/smart-app-launch/backend-services.html