Access Control Decision Guide | Medplum
Section 1: Use Case & Participants
1.1 Who are the users?
- Patients only
- Providers / staff / partners (clients or outside vendors) only
- Both
1.2 What is your organizational structure?
- Single organization
- Multiple locations of one organization
- Multiple independent organizations (MSO-style)
- Departments or service lines within one organization
- Combination
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:
- Internal term for tenants? (clinics, programs, sites, accounts)
- Grouping is organizational, functional, or per-patient?
- Is there an organizational hierarchy (e.g., region → clinic)?
| Situation | Approach |
|---|---|
| Clinics, practices, locations (MSO) | Organization |
| Service lines, departments | HealthcareService |
| Dedicated team per patient | CareTeam per patient |
2b Patient and Practitioner Sharing
Questions:
- Can a patient be enrolled in more than one tenant at a time?
- Are practitioners isolated to their tenant, or visible across all tenants (global directory)?
| 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:
- What are the role names? (physician, RN, MA, front desk, billing, care coordinator, medical director)
- If multi-tenant: for each role, which tenants does it cover? (one, several, all)
- Any oversight roles that need visibility across all tenants without per-tenant enrollment?
| 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:
- For each role: which resource types does it read / write / search / delete?
- Any partial FHIR interactions? (create-only, read-without-search, etc.)
| 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:
- Who invites/removes users and manages memberships?
- Who manages Bots, ClientApplications, AccessPolicies?
- Any super admin needs? (project creation via API, overwriting protected fields)
- Should admins be blocked from clinical data?
| 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:
- Self-registration or staff-invite?
- Are caregivers / proxies needed? (parents, adult children, guardians)
- One caregiver with multiple patients? One patient with multiple caregivers?
- How is the caregiver relationship established and revoked?
| 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:
- Patient-context launch, provider-context launch, or standalone?
- Which scopes?
- Offline access needed?
| 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:
- Restrict by IP range? (VPN, on-prem devices)
- Any roles that bypass?
| 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:
- When does a patient get enrolled in a tenant? (registration, first appointment, referral, insurance, geography)
- Who is authorized to move a patient between tenants?
- On transfer: do records follow the patient, stay with the original tenant, or become shared?
| 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:
- Terminology (CodeSystems, ValueSets) shared or tenant-specific?
- Questionnaires shared or per-tenant?
- PlanDefinitions / ActivityDefinitions global or per-tenant?
- Any resources shared across projects (not just tenants)?
| 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 |