RBAC Matrix
On this page
Identity provider
Canopy delegates worker authentication to Keycloak (canopy realm). Worker identities live in Keycloak; service tokens are JWTs validated against the realm’s JWKS. canopy-portal (applicant-facing) does not use Keycloak: reference-number auth (HH-[a-f0-9]{8} code + passcode) is LIVE, verified by canopy-portal against canopy-applications' verify-credential endpoint, and applicant sessions are opaque Redis tokens per ADR-008 / ADR-026 — no applicant JWT.
-
Bearer-token validation:
canopy-auth::middleware::require_bearer_auth -
Role extraction:
Claims::roles()reads Keycloak realm-roles claim -
Role enforcement:
Claims::require_*helpers incanopy-auth::claims
Role catalogue
Six realm roles, defined in Keycloak and enforced by canopy-auth::Claims helpers in crates/canopy-auth/src/claims.rs:
| Role | Audience | Typical responsibilities |
|---|---|---|
|
Constituent |
Submit application, check status, view notices for own household. Reference-number authentication ( |
|
Frontline DFCS staff |
Read case data, run intake, file appeals on behalf of applicant. Read-only on most determination endpoints; cannot run determinations or sign artefacts. |
|
Trained eligibility staff |
Run determinations ( |
|
DFCS supervisor / approval authority |
All |
|
State QC reviewers |
Read-only access to case data, audit-event search, chain-status, FNS-7176 QC universe extracts. Subset of |
|
System operators |
Full access. Used for system-administration endpoints + override scenarios. Membership tightly controlled at the Keycloak realm level. |
Role hierarchy
The Claims::require_*_or_above helpers encode the inclusion lattice. or_above means any of the named roles satisfies the guard:
admin
▲
supervisor ← require_supervisor_or_above: {supervisor, admin}
▲
eligibility_specialist ← require_eligibility_specialist_or_above:
▲ {eligibility_specialist, supervisor, admin}
caseworker ← require_caseworker_or_above:
▲ {caseworker, eligibility_specialist, supervisor, quality_control, admin}
quality_control (sibling — read)
Note: quality_control is a sibling of caseworker — it satisfies require_caseworker_or_above (read-side) but does not satisfy require_eligibility_specialist_or_above (write-side). This is the key separation-of-duties contract for QC reviewers.
applicant is its own root — require_applicant is mutually exclusive with all worker-side guards.
Endpoint enforcement (representative)
Drawn from grep of claims.require_* call sites across the workspace as of 2026-04-28. The full surface is in each service’s src/api/ directory; this is the auditor-relevant subset.
| Endpoint | Guard | Notes |
|---|---|---|
|
|
Determination is a write action that produces a signed binding artefact. Caseworker-only roles cannot run determinations. |
|
|
Same gate as the program services it dispatches to. Bearer token forwarded to each program service per ADR-001 + ADR-002. |
|
|
Read-side: caseworker, QC, eligibility_specialist, supervisor, admin all permitted. |
|
|
Write-side: caseworker + QC excluded (read-only). |
|
|
Intake is permitted to caseworker and above — caseworker-side intake is the primary use case. |
|
|
Federal report generation requires sign-off authority — carried by
the worker’s EXCHANGED per-target token ( |
Unified chain-verification namespace (#1205, ADR-014 Amendment 9): |
service-class token OR |
Pub 1075 §9 reportable surface, BOTH families — |
|
|
Read-side; QC reviewers can search audit events. |
|
|
Breach disposition is admin-only (incident-response runbook). |
TANF personal-responsibility actions ( |
|
Sanctions + IPV referrals require supervisor sign-off. |
|
|
Expedited determinations 7 CFR 273.2(i). |
|
|
Pub 1075 AC-6 least-privilege. When the request resolves a HUMAN (an exchanged worker bearer), they must either carry |
|
|
Service-caller-only: post-#1443 a service bearer never transports a human, so the old delegated-supervisor bar is gone (it could no longer fire). Workers of any role are 403 — assignment mutations are system provisioning; a future worker-delegated surface would flip to |
|
|
Soft-delete via |
|
|
Read-side; any service-class caller. Hot-path consumed by canopy-enrollment’s RBAC gate. |
Cross-service token flow
Per ADR-001 program-service isolation, services authenticate to each other via the same Keycloak token the worker presented to the orchestrator:
-
Worker → canopy-eligibility: bearer token (Keycloak-issued)
-
canopy-eligibility → canopy-medicaid (and other program services): forwards the worker’s bearer token in the
Authorization: Bearer …header (orchestrator.rs:412 — fixed in #338) -
Each program service runs its own
require_eligibility_specialist_or_aboveguard against that token
This means the worker’s authority — not the orchestrator’s — gates every downstream action. There is no service-account that programmatically escalates privilege.
Service-to-service calls that do use a separate API key (canopy-snap → canopy-verification’s IEVS adapter, canopy-reporting → upstream services) are scoped to internal endpoints not exposed publicly.
Service-account secrets
| Secret | Purpose |
|---|---|
|
Internal HTTP calls between services that don’t carry a worker token (e.g. canopy-reporting → program services). Default |
|
Per-program ECDSA P-256 private key for determination JWS signing (ADR-002). Production sets via env; dev uses |
|
Per-program ECDSA P-256 public key on the orchestrator side. Production sets via env; dev falls back to |
|
AES-256-GCM key for SSN field-level encryption in canopy-persons. 32 bytes, base64-encoded. |
Key rotation procedures: Security Operations §Key Rotation Runbook + Signing Key Rotation Runbook.
Drift between docs
The implementation-guide.adoc Authentication & Authorization section once listed roles admin / supervisor / eligibility_worker / intake_worker / fiscal_officer. Those names predate the Keycloak realm-roles convention adopted with canopy-auth. The authoritative list is the one above (driven by the actual Claims::require_* helpers). The implementation guide will be updated in a follow-up doc pass to converge on these names.