Skip to main content

Tenant Isolation Approaches in Medplum

· 12 min read
Finn Bergquist
Forward Deployed Engineer, Medplum

In healthcare applications, practitioners often work across multiple organizational boundaries. A doctor might work at multiple clinics, a nurse might be part of several care teams, or a care coordinator might manage patients across different healthcare services. Each of these—clinics, care teams, and healthcare services—represents a distinct tenant in your system: a collection of resources (patients, observations, encounters, etc.) that should be logically grouped together.

In Medplum, you can build your tenancy model around any FHIR resource type. Common examples include:

  • Organization: Different clinics, practices, or healthcare organizations
  • HealthcareService: Different departments or services (e.g., Cardiology Department, Oncology Department)
  • CareTeam: Different care teams (e.g., Diabetes Care Team, Hypertension Care Team)

For a comprehensive guide on how to set up multi-tenancy in Medplum—including data modeling, compartments, propagation, and user enrollment—see our Multi-Tenant Access Control documentation.

This blog post focuses on a specific challenge: What happens when a user belongs to multiple tenants? And more importantly, how can you ensure your application restricts access to only one tenant at a time?

Understanding API-Level Security and Isolation

Before diving into the solutions, it's important to understand what API-level security and isolation means, especially for non-technical readers.

When a user logs into your application, they receive an authentication token (think of it as a digital key). This token is used to make requests to the Medplum API server to read or modify patient data, medical records, and other healthcare information.

API-level isolation refers to security controls that are enforced by the Medplum server itself—not just by your application's user interface. When properly configured, the server checks every request against the user's permissions and only allows access to data the user is authorized to see.

Why This Matters: The Security Risk

Here's the critical security concern: If a bad actor obtains a user's authentication token and knows how to construct FHIR API requests, they could potentially access data from all tenants that user has access to—even if your application's UI only shows one tenant at a time.

The risks include:

  1. Unauthorized Data Access: An attacker with a stolen token could bypass your application's UI restrictions entirely and directly query the API to retrieve patient data from multiple tenants simultaneously.

  2. Data Exfiltration: They could systematically extract sensitive health information (PHI) from all tenants the user belongs to, not just the one currently displayed in your application.

  3. Compliance Violations: Accessing data across tenant boundaries could violate HIPAA, state privacy laws, or organizational policies that require strict data isolation between different clinics, departments, or care teams.

  4. Cross-Tenant Data Leakage: Even if your UI correctly filters data, an attacker making direct API calls could discover relationships, patient overlaps, or other sensitive information that should remain isolated between tenants.

The key question: Does your security model rely solely on your application's UI to restrict access, or does the API server itself enforce single-tenant isolation?

This blog post explores different approaches to ensure that even if someone gets hold of an authentication token, the API server itself will restrict access to only one tenant's data at a time—providing true API-level security.

How to Ensure Your Application Can Be Contained to Just One Tenant at a Time When a User Belongs to Multiple Tenants

The Challenge: Multiple Tenant Memberships

When a Practitioner user belongs to multiple tenants, their ProjectMembership will have multiple entries in the access array, each with different tenant parameters:

In this scenario, the user's API-level access includes data from both clinics. But how do you ensure your application is contained to just one tenant at a time?

{
"resourceType": "ProjectMembership",
"access": [
{
"parameter": [
{
"name": "organization",
"valueReference": {
"reference": "Organization/clinic-a",
"display": "Downtown Clinic"
}
}
],
"policy": {
"reference": "AccessPolicy/mso-policy"
}
},
{
"parameter": [
{
"name": "organization",
"valueReference": {
"reference": "Organization/clinic-b",
"display": "Uptown Clinic"
}
}
],
"policy": {
"reference": "AccessPolicy/mso-policy"
}
}
]
}

Tenant Isolation Approaches: Comparison Table

ApproachAPI-Level Isolation (Enrolled Tenants)API-Level Isolation (Per Tenant)Application-Level IsolationUse Case
Option 1: All TenantsYesNoNoCross-tenant visibility acceptable
Option 2: _compartment ParameterYesNoYesNeed UI-level tenant restriction acceptable, API level access to all enrolled tenants
Option 3: Multiple MembershipsYesYesYesNeed strict API-level tenant isolation

Column Descriptions:

  • API-Level Isolation (All Enrolled Tenants): Whether the API restricts access to only the tenants the user is enrolled in via their ProjectMembership.

  • API-Level Isolation (Per Tenant): Whether the API restricts a User's access to only one tenant at a time, even if the User is enrolled across multiple tenants.

  • Application-Level Isolation: Whether the UI restricts what's displayed to a single tenant.

Choosing the Right Approach

Choose Option 1 if cross-tenant visibility is acceptable and you want the simplest implementation.

Choose Option 2 if you need UI-level tenant isolation but want users to be able to switch between tenants without re-authenticating. This approach maintains API-level access to all tenants and is also able to restrict what's displayed in the UI to only a single tenant at a time. It is much easier to implement Option 2 than Option 3.

Choose Option 3 if you need strict API-level single tenant access for security or compliance reasons. This is the most secure approach but requires users to sign in separately for each tenant they need to access with a separate ProjectMembership.

Option 1: Allow Access to All Enrolled Tenants

Description: The user can view Patients and resources from all enrolled tenants simultaneously.

Implementation: No additional restrictions needed. The AccessPolicy already grants access to all enrolled tenants, and your application can display data from all tenants.

Use Case: This approach works well when cross-tenant visibility is acceptable or desired. For example, a care coordinator who needs to see all patients across multiple clinics they manage.

Option 2: Frontend-Level Restriction with _compartment

Description: Keep the ProjectMembership and AccessPolicy configuration the same, but add the _compartment search parameter to all frontend queries to scope results to a specific tenant. The _compartment search parameter may look familiar because it is used in the AccessPolicy critertia. We can also use _compartment on the query itself, in combination with the AccessPolicy criteria, to further restrict the data that is returned to the user to only the data for the currently selected tenant.

Implementation: At the application level, maintain state for the currently selected tenant, and append _compartment=<current_tenant_ref> to all search queries.

Example:

// User selects "Downtown Clinic" in the UI
const currentTenant = 'Organization/clinic-a';

// All queries include _compartment filter
const patients = await medplum.search('Patient', {
_compartment: currentTenant
});

const observations = await medplum.search('Observation', {
_compartment: currentTenant
});

Use Case: When you need UI-level tenant isolation but want to maintain API-level access to all tenants. This allows users to switch between tenants in the UI without re-authenticating, while ensuring the UI only displays data from the selected tenant.

Option 3: Multiple ProjectMemberships

Description: Create a separate ProjectMembership for each tenant. Each membership has its own AccessPolicy with a single tenant parameter, enforcing isolation at the API level.

Multiple ProjectMemberships is an advanced Medplum feature. ProjectMemberships are a crucial part of access control to the Medplum data store and determine what resources a user can read, write, or modify. Misconfiguring ProjectMemberships or AccessPolicies can result in Users being locked out of necessary resources or gaining unauthorized access to data.

Before implementing multiple memberships:

  • Thoroughly understand AccessPolicies and how they work
  • Test extensively in a development environment
  • Consider consulting with Medplum support or the community if you're unsure ::

Implementation: Use the /admin/projects/:projectId/invite endpoint with forceNewMembership: true to create additional memberships for the same user.

With multiple ProjectMemberships, each membership grants access to only one tenant, ensuring strict API-level isolation:

Example:

// First membership - Downtown Clinic
await medplum.post('admin/projects/:projectId/invite', {
resourceType: 'Practitioner',
firstName: 'Jane',
lastName: 'Smith',
email: 'dr.smith@example.com',
password: 'secure-password',
membership: {
access: [
{
policy: { reference: 'AccessPolicy/mso-policy' },
parameter: [
{
name: 'organization',
valueReference: { reference: 'Organization/clinic-a' }
}
]
}
],
identifier: [
{
system: 'https://medplum.com/identifier/label',
value: 'Downtown Clinic'
}
]
}
});

// Second membership - Uptown Clinic
await medplum.post('admin/projects/:projectId/invite', {
resourceType: 'Practitioner',
firstName: 'Jane',
lastName: 'Smith',
email: 'dr.smith@example.com',
forceNewMembership: true, // Required for creating additional membership
membership: {
access: [
{
policy: { reference: 'AccessPolicy/mso-policy' },
parameter: [
{
name: 'organization',
valueReference: { reference: 'Organization/clinic-b' }
}
]
}
],
identifier: [
{
system: 'https://medplum.com/identifier/label',
value: 'Uptown Clinic'
}
]
}
});

Sign-In Flow: When a user with multiple ProjectMemberships signs in, Medplum automatically prompts them to choose which membership to use for their session. Each membership can have a custom label (via the https://medplum.com/identifier/label identifier system) to help users distinguish between them.

Medplum Multiple ProjectMemberships Sign-In Screenshot

Use Case: When you need strict API-level tenant isolation. This ensures that once authenticated, the user's API access is limited to only the selected tenant's data. This is the most secure approach and is ideal for scenarios where regulatory requirements or organizational policies mandate strict data isolation.