Skip to main content

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:

  1. Maps the HTTP method to a FHIR action (GET → read, POST → create, PUT/PATCH → update, DELETE → delete)
  2. Checks the user’s roles and permissions against the requested resource and action
  3. For PHI resources (Patient, Encounter, Observation, Condition, etc.) on specific ID reads/updates/deletes: checks the Consent table for active consent
  4. Returns a FHIR OperationOutcome with 403 Forbidden on denial

Roles

RoleDescription
adminFull system access — all resources, all actions; also bypasses the consent check
practitionerClinical access — read/write for assigned patient resources
patientSelf-service — read/write only their own records
api-clientScoped access based on API key scopes
emergency-responderEmergency 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:

ScopeGrants
Patient.readRead Patient resources
Patient.createCreate Patient resources
Patient.updateUpdate Patient resources
Patient.deleteDelete Patient resources
Patient.*All Patient operations
Observation.*All Observation operations
*Full access to all resources and actions (use with care)

Do not use write or *.read. The validation pattern syntactically accepts Resource.write, but the ABAC engine only matches the read/create/update/delete actions, so a .write scope authorizes no writes at runtime. To allow writes, grant create, update, and delete explicitly (or Resource.*). The *.read wildcard 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.

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 / deny blocks 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.