Access Control Model
ESUS uses Attribute-Based Access Control (ABAC) — every protected route evaluates the requesting user’s roles and permissions before allowing access. For PHI resources, an additional patient consent check is performed.
How It Works
Every FHIR endpoint is protected by the guardResource middleware, which:
- Maps the HTTP method to a FHIR action (
GET → read,POST → create,PUT/PATCH → update,DELETE → delete) - Checks the user’s roles and permissions against the requested resource and action
- For PHI resources (Patient, Encounter, Observation, Condition, etc.) on specific ID reads/updates/deletes: checks the
Consenttable for active consent - Returns a FHIR
OperationOutcomewith403 Forbiddenon denial
Roles
| Role | Description |
|---|---|
admin | Full system access — all resources, all actions; also bypasses the consent check |
practitioner | Clinical access — read/write for assigned patient resources |
patient | Self-service — read/write only their own records |
api-client | Scoped access based on API key scopes |
emergency-responder | Emergency override access (fully audited) |
Managing Roles
# List all roles in your organization
curl https://api.esus.health/roles \
-H "Authorization: Bearer TOKEN"
# Create a custom role
curl -X POST https://api.esus.health/roles \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "lab-technician",
"permissions": [
{ "resource": "Observation", "action": "create" },
{ "resource": "Observation", "action": "read" },
{ "resource": "DiagnosticReport", "action": "create" },
{ "resource": "DiagnosticReport", "action": "read" },
{ "resource": "Specimen", "action": "read" }
]
}'
Assigning Roles to Users
# Assign a role to a user
curl -X POST https://api.esus.health/roles/users/:userId/assign \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{ "roleId": "b8c2d3e4-f5a6-7890-bcde-f01234567890" }'
# Remove a role from a user
curl -X DELETE https://api.esus.health/roles/users/:userId/:roleId \
-H "Authorization: Bearer TOKEN"
Viewing Available Permissions
curl https://api.esus.health/roles/permissions \
-H "Authorization: Bearer TOKEN"
Response:
[
{ "resource": "Patient", "action": "read" },
{ "resource": "Patient", "action": "create" },
{ "resource": "Patient", "action": "update" },
{ "resource": "Patient", "action": "delete" },
{ "resource": "Observation", "action": "read" },
...
]
API Key Scopes
When using API keys, access is controlled by scopes defined at key creation time. Scopes follow the Resource.action pattern, where action must be one of read, create, update, delete, or the * wildcard. The global * wildcard (all resources and actions) is also accepted. Each scope maps directly to an ABAC permission:
| Scope | Grants |
|---|---|
Patient.read | Read Patient resources |
Patient.create | Create Patient resources |
Patient.update | Update Patient resources |
Patient.delete | Delete Patient resources |
Patient.* | All Patient operations |
Observation.* | All Observation operations |
* | Full access to all resources and actions (use with care) |
Do not use
writeor*.read. The validation pattern syntactically acceptsResource.write, but the ABAC engine only matches theread/create/update/deleteactions, so a.writescope authorizes no writes at runtime. To allow writes, grantcreate,update, anddeleteexplicitly (orResource.*). The*.readwildcard is rejected by the validator — the only resource-level wildcard is the bare*.
Emergency Override
For emergency situations, only users with the emergency-responder role can bypass the consent check using the X-Emergency-Override header:
curl https://api.esus.health/fhir/Patient/3fa85f64-5717-4562-b3fc-2c963f66afa6 \
-H "Authorization: Bearer TOKEN" \
-H "X-Emergency-Override: true"
Important: All emergency override accesses are:
- Immediately written to the audit log
- Recorded with a tagged
resourceType(Resource[emergency]) to distinguish them from normal access - Subject to post-access review by administrators
Sending this header without the emergency-responder role does not return 403: an unauthorized_emergency_override audit event is logged and the request continues through normal authorization (the outcome is decided by your ordinary permissions). Administrators do not use this header — they bypass the consent check via their admin role, not via emergency override.
Consent-Gated Access
For PHI resources, the ABAC engine also checks whether an active Consent record exists for the patient. The check queries consent per patient and organization (patientId + status='active' + organizationId) with the scope "treatment" — it is not evaluated per individual practitioner. If no active treatment consent exists for the patient in your organization, access is denied even if the user has the correct role.
The Consent resource’s
provision.actor/denyblocks are not consulted by this check: consent cannot allow or deny a specific practitioner. It applies at the patient + organization level.
See Patient Consent for how to create and manage consent records.