ADR-005: Keycloak for OIDC/RBAC
On this page
Context
CRAIG requires authentication and authorization for all API and web UI access. Child welfare systems typically integrate with state-level identity providers (Active Directory, SAML). We needed an identity provider that supports OIDC, RBAC, and can federate with external identity sources.
Decision
Use Keycloak as the OIDC identity provider with a craig realm containing:
-
6 realm roles:
admin,supervisor,caseworker,eligibility_worker,icpc_coordinator,readonly -
2 clients:
-
craig-api— public, Direct Access Grant (ROPC) for CLI and tests -
craig-ui— public, Authorization Code + PKCE for web UI
-
Rationale
-
Industry standard: Keycloak is the most widely deployed open-source identity provider, used in government and healthcare.
-
OIDC support: Native OpenID Connect support with JWT access tokens, JWKS endpoint, and standard flows.
-
Flexible federation: Keycloak can federate with LDAP, Active Directory, SAML 2.0 IdPs — essential for state agency deployments.
-
Role-based access: Realm roles are embedded in JWT
realm_access.rolesclaim. Services extract roles viacraig-auth::Claimsand enforce viarequire_role(). -
Self-contained devstack: Keycloak’s realm import feature (
--import-realm) allows the devstack to start with pre-configured users, roles, and clients from a JSON file.
Role Hierarchy
admin → full access to all services
supervisor → caseworker + management actions (approve plans, manage homes)
caseworker → core case management (CRUD on cases, referrals, placements)
eligibility_worker → eligibility-specific actions (future)
icpc_coordinator → ICPC request management
readonly → view-only access (future)
Helper functions: require_role(claims, Role::CaseworkerOrAbove) — combines caseworker, supervisor, and admin.
Dual URL Pattern
In containerized deployments:
-
KEYCLOAK_ISSUER— public URL (e.g.,https://auth.example.com/realms/craig) used for JWTissclaim validation -
KEYCLOAK_URL— internal URL (e.g.,http://keycloak:8080/realms/craig) used for JWKS fetching
Both must resolve to the same Keycloak realm.
Consequences
-
Keycloak is an additional infrastructure dependency (Java-based, ~500MB image).
-
Token expiration (30 minutes default) means long-running CLI sessions may need to re-authenticate.
-
Role changes in Keycloak require the user to obtain a new token — existing JWTs retain old roles until expiry.