Access Control Domain - Workflow Narrative
A conversational guide to understanding security and permissions workflows
What is the Access Control Domain?
Not everyone should see everything. A billing clerk shouldn't access clinical notes. A nurse might see different patients than a physician. Some patients are sensitive and require restricted access.
The Access Control domain manages who can do what. It combines Role-Based Access Control (RBAC) for functional permissions with Access Control Lists (ACL) for patient-level restrictions.
Two Layers of Security
RBAC - What Can You Do?
RBAC controls functional access based on roles:
- Can you create patients?
- Can you view medical records?
- Can you manage billing?
- Can you administer users?
ACL - Which Patients Can You See?
ACL controls data access at the patient level:
- Which patients are you allowed to view?
- Do you have write access or read-only?
- Is your access permanent or time-limited?
Together, these create a comprehensive security model. You might have the patient.read permission (RBAC), but only for patients on your care team (ACL).
Roles and Permissions
System Roles
The system comes with predefined roles:
- superuser - Full system access
- admin - Organization administration
- physician - Clinical access
- nurse - Clinical access (may be scoped)
- receptionist - Front desk operations
- billing_clerk - Financial operations
- viewer - Read-only access
Granular Permissions
Each role has associated permissions:
physician:
- patient.read
- patient.write
- medical_record.read
- medical_record.write
- appointment.read
receptionist:
- patient.read
- patient.write
- appointment.read
- appointment.write
Organization Assignment
Users get roles within an organization context. You might be an admin at Clinic A but just a viewer at Clinic B if you're a member of both.
Permission Checking
The Enforcement Pattern
Throughout the codebase, endpoints are decorated with permission requirements:
@router.get("/patients")
async def get_patients(
current_user = Depends(require_org_permission("patient.read"))
):
...
How require_org_permission Works
- Extract the user from the authenticated request
- Get their organization membership
- Get their role(s) in that organization
- Check if any role has the required permission
- If yes, proceed; if no, return 403 Forbidden
Multiple Permissions
Some operations require multiple permissions. Creating a patient might check both patient.write and organization.member.
Access Control Lists (ACL)
Patient-Level Security
ACL adds another layer. Even if you have patient.read permission, ACL can restrict which patients you see.
ACL Grants
An ACL grant gives a specific user access to a specific patient:
- user_id - Who is granted access
- patient_id - Which patient
- permission - READ or WRITE
- expires_at - Optional expiration date
- granted_by - Who created the grant
- reason - Why access was granted
Grant Sources
Grants can come from:
- direct - Manually assigned by admin
- encounter - Created when practitioner sees patient
- care_team - Part of the care team
- referral - Referred by another provider
ACL Enforcement
When ACL is Checked
ACL enforcement happens at the service layer. When fetching patients:
def get_patients_paginated(self, ..., user_id, user_roles):
# If ACL is enabled and user isn't exempt
if acl_enabled and not is_acl_exempt_role(user_roles):
# Filter to only patients they have ACL grants for
statement = statement.join(ACL).filter(ACL.user_id == user_id)
Exempt Roles
Some roles bypass ACL - they can see all patients:
- Administrators (for system management)
- Care coordinators (for access management)
- Billing clerks (for organization-wide billing)
These exempt roles are configurable per organization.
Non-Exempt Roles
Clinical roles like physicians and nurses are typically subject to ACL. They only see patients they have an explicit grant for.
Access Modes
Full Access Mode
When ACL is disabled (or user is exempt), all patients in the organization are visible. The standard multi-tenant filter (organization_id) still applies.
ACL Mode
When ACL is enabled for a user, patients are filtered by their grants:
- Only shows patients with valid (non-expired) grants
- Respects the permission level (READ vs WRITE)
- The UI might show different options based on permission
Data Masking
Some organizations prefer to show that a patient exists but mask details. ACL configuration can specify:
- full - Complete patient data if accessible
- basic - Limited data without full access
- none - Patient not shown at all
Auto-Grant on Clinical Interaction
Automatic Access
When a practitioner creates an encounter with a patient, they probably need ongoing access to that patient. Auto-grant rules handle this.
Organization configuration can specify:
- auto_grant_on_encounter - Grant access when encounter created
- default_grant_permission - READ or WRITE
- default_grant_duration_days - How long access lasts (e.g., 180 days)
How It Works
- Practitioner creates encounter with patient
- System checks auto-grant configuration
- If enabled, creates ACL grant for practitioner → patient
- Grant has configured permission level and expiration
This eliminates manual grant management for routine clinical interactions.
Managing ACL Grants
Viewing Grants
Administrators can view who has access to which patients:
- By patient: "Who can see John Smith?"
- By user: "Which patients can Dr. Jones access?"
Creating Grants
Admins can manually create grants:
POST /acl/grants
{
"user_id": "usr_physician",
"patient_id": "pat_123",
"permission": "READ",
"expires_at": "2025-12-31",
"reason": "Covering for Dr. Smith vacation"
}
Revoking Grants
Grants can be revoked:
- Immediately by deletion
- By setting an early expiration
- By deactivating the grant
Expiration Handling
A background job can clean up expired grants. Alternatively, queries filter by expires_at > now() so expired grants aren't used.
Role-Based ACL Exemptions
Configuration
Organizations configure which roles are exempt from ACL:
{
"acl_exempt_roles": ["admin", "care_coordinator", "billing_clerk"]
}
Practical Effect
- Physicians and nurses: ACL enforced, see their patients only
- Administrators: ACL exempt, see all patients for management
- Billing clerks: ACL exempt, see all patients for billing
This balances security with practical needs.
RBAC Administration
Role Assignment
Users are assigned roles through organization membership:
POST /organizations/{org_id}/members
{
"user_id": "usr_abc",
"role": "physician"
}
Custom Roles
Organizations can create custom roles with specific permission sets:
{
"name": "senior_nurse",
"display_name": "Senior Nurse",
"permissions": ["patient.read", "patient.write", "medical_record.read"]
}
Role Hierarchy
Some implementations have role hierarchies (admin inherits physician permissions). The permission check evaluates all inherited permissions.
Security Audit Trail
Access Logging
Significant access control events are logged:
- User logged in
- Permission check succeeded/failed
- ACL grant created/modified/revoked
- Role assignment changed
Compliance Value
These logs support compliance requirements:
- HIPAA audit requirements for PHI access
- Internal security reviews
- Investigation of access patterns
Integration Points
With Patient Domain
Patient queries incorporate ACL filtering. The get_patients_paginated method takes user_id and user_roles to apply appropriate filtering.
With Encounter Domain
Encounter creation can trigger auto-grants. The encounter service coordinates with ACL service.
With Notification Domain
Some notifications are security-related:
- "You've been granted access to patient John Smith"
- "Your access to patient Jane Doe expires in 7 days"
Key Takeaways
-
RBAC for functions - What can you do?
-
ACL for data - Which patients can you see?
-
Exempt roles exist - Some roles bypass ACL
-
Auto-grant simplifies management - Clinical interaction creates grants
-
Expiration provides time-limits - Temporary access is supported
-
Audit trails track access - For compliance and security
-
Organization scoping - Roles and ACL are per-organization
Next: Read about the Organization Domain to understand multi-tenancy