Access Control Decision Guide | Medplum

Section 1: Use Case & Participants

1.1 Who are the users?

1.2 What is your organizational structure?

Section 2: Multi-tenancy

Isolation uses compartments (meta.compartment) matched by parameterized access policies.

Design principle: Push complexity into enrollment Bots, not policies. Each patient is tagged with their tenant(s); each practitioner's ProjectMembership.access lists every tenant they work in.

2a Tenant Modeling

Questions:

Situation Approach
Clinics, practices, locations (MSO) Organization
Service lines, departments HealthcareService
Dedicated team per patient CareTeam per patient

2b Patient and Practitioner Sharing

Questions:

Situation Approach
One tenant per patient Single compartment on the Patient
Patient shared across tenants Multiple compartments, one per tenant
Practitioners shared (global directory) Leave unassigned; policy reads without _compartment filter
Practitioners isolated per tenant Tag with tenant compartment; policy filters by _compartment

Section 3: Roles & Permissions

For each role: name it, scope it (which tenants, if multi-tenant), say what resources it touches, and narrow as needed.

3a Role Inventory & Scope

Questions:

Role scope Modeling approach
One tenant Single ProjectMembership.access entry parameterized with that tenant
Multiple tenants, combined view One access entry per tenant, same policy
Org-wide / cross-tenant oversight Unparameterized policy, no _compartment filter
Tenants contractually can't share data (e.g., locum across competitor clinics) Separate logins per tenant — see callout below

3b Permission Mechanics

Questions:

Situation Approach
Full CRUD on a resource Standard resource entry, no interaction restriction
Read-only readonly: true
Partial interactions Explicit interaction array

3c Narrowing What a Role Can Do

Field-level — partial field access (e.g., billing sees Patient minus diagnoses):

Situation Approach
Readable but not writable readonlyFields
Not visible hiddenFields (masks output only)

Additional Write constraints

Situation Approach
Prevent field change in terminal state writeConstraint on %before
Require field at transition writeConstraint on %after + field presence
Update-only Prefix with %before.exists() implies ...
Create-only Prefix with %before.exists().not() implies ...

Section 4: Admin Structure

Questions:

Situation Approach
Standard admin ProjectMembership.admin
Admin with narrowed clinical access Admin flag + dedicated AccessPolicy
Admin blocked from clinical content Admin flag + hiddenFields on clinical resources
Project creation, overwriting protected fields Super admin — server operators only
Separate "user manager" vs. "tech admin" Two AccessPolicies stacked on admin flag

Appendix: Additional Decisions

A. Open Registration & Caregivers

Questions:

Situation Approach
Invite-only Standard ProjectMembership with patient policy
Open self-registration Set Project.defaultPatientAccessPolicy; enable open registration
Patient sees own data Parameterized policy with %patient on clinical resources
Single caregiver, single patient One access entry with %patient
One caregiver, multiple patients Multiple access entries, different %patient
Multiple caregivers per patient Each caregiver has their own ProjectMembership
Revoke caregiver Remove the corresponding access entry
Time-bound caregiver access (e.g., expires at age 18) Application-layer enforcement — not native

B. SMART on FHIR

Questions:

Situation Approach
Your own frontend Not SMART — standard ClientApplication + AccessPolicy
Third-party, patient-context SMART 2.0.0, launch/patient
Third-party, provider-context SMART 2.0.0, launch
Standalone patient app patient/*.rs or narrower
Offline access Add offline_access

C. IP Access Rules

Questions:

Situation Approach
VPN-only Allow VPN ranges + wildcard block
On-prem only Allow clinic network + wildcard block
Mixed Different AccessPolicies per role

D. Enrollment & Reassignment Workflows

Questions:

Situation Approach
Patient enrollment Bot calls $set-accounts on the triggering event; use propagate: true to tag related resources
Transfer, records follow Update compartment with propagate: true; old tenant loses access
Transfer, records stay / shared Add new compartment (don't replace); both tenants retain access

E. Shared vs. Tenant-Specific Resources

Questions:

Situation Approach
Shared standard terminology Leave unassigned; unfiltered read in policy
Tenant-specific Questionnaires $set-accounts tags with tenant; filter by _compartment
Mixed (some global, some per-tenant) Two resource entries in policy: one filtered, one unfiltered
Global Bots Leave unassigned; Bots run as ClientApplication
Reference data shared across multiple projects Super admin creates a linked project (read-only view)

F. Role-Aware UI

Situation Approach
UI changes based on role AccessPolicy.basedOn + /auth/me