| features |
|
|||
|---|---|---|---|---|
| languages |
|
This example demonstrates how to implement role-based access control (RBAC) for FHIR resources using Keycloak and SMART on FHIR V2 scopes.
The demo application is a Bun/TypeScript web application that authenticates users through Keycloak and makes requests to the Aidbox FHIR Server. Depending on the user's role in Keycloak, the same API call (GET /fhir/Observation) returns different results:
- Physician role: Can access all observations (laboratory results and vital signs)
- Lab Technician role: Can only access laboratory observations with final status
This is achieved by mapping Keycloak roles to SMART on FHIR V2 scopes, which Aidbox automatically enforces without additional configuration.
The my-app client is configured in Keycloak with:
- Client ID:
my-app - Client authentication enabled (confidential client)
- Standard OpenID Connect flow with authorization code grant
Keycloak roles are structured in two layers:
Basic Roles (represent individual SMART on FHIR V2 scopes):
user/Patient.rs- read and search Patient datauser/Encounter.rs- read and search Encounter datauser/Observation.rs- read and search all Observation datauser/Observation.rs?category=laboratory&status=final- read and search laboratory Observations with final status only
Composite Roles (combine basic roles into meaningful job functions):
-
physician- Full clinical access:user/Patient.rsuser/Encounter.rsuser/Observation.rs
-
lab_technician- Limited to lab results:user/Patient.rsuser/Observation.rs?category=laboratory&status=final
Two test users are created in Keycloak:
- physician (password:
password) - assigned thephysiciancomposite role - lab_technician (password:
password) - assigned thelab_techniciancomposite role
Keycloak is configured with a custom protocol mapper to:
- Automatically resolve composite roles into their constituent basic roles
- Include the resolved SMART scopes in the
scopeclaim of the access token - Add the
atv(authorization token version) claim with value"2"to indicate SMART on FHIR V2
Example access token for the lab_technician user:
{
"scope": "user/Patient.rs user/Observation.rs?category=laboratory&status=final",
"atv": "2"
}When Aidbox receives this token, it automatically enforces the SMART scopes, allowing only the specified resources and applying query parameter restrictions.
Aidbox is preconfigured via init-bundle.json with:
- TokenIntrospector - Validates JWT tokens from Keycloak using JWKS
- AccessPolicy - Allows requests with tokens issued by Keycloak
No additional configuration is required for SMART scope enforcement. Aidbox automatically enforces access control when it detects:
atv: "2"claim in the access token (indicates SMART on FHIR V2)- SMART scopes in the
scopeclaim (e.g.,user/Patient.rs,user/Observation.rs?category=laboratory)
When both conditions are met, Aidbox:
- Parses the SMART scopes to determine allowed resources and operations
- Applies resource type restrictions (e.g., only
Patient,Observation) - Enforces query parameter filters (e.g.,
?category=laboratory&status=final) - Returns only data that matches the scope constraints
For example, a request to GET /fhir/Observation with scope user/Observation.rs?category=laboratory&status=final will automatically filter results to only laboratory observations with final status.
Learn more about SMART scopes in Aidbox.
- Docker
-
Run docker compose
docker compose up --build
-
Initialize Aidbox instance Navigate to Aidbox UI and initialize the Aidbox instance.
-
Test the access control
Navigate to the demo application at http://localhost:3000
Login with the physician credentials:
Username: physician Password: passwordExpected Result: You should see both observations:
- Hemoglobin (laboratory observation with final status)
- Blood Pressure (vital signs observation)
This is because the physician role has the
user/Observation.rsscope, which grants unrestricted read access to all Observation resources.Logout by clicking the Start Over button and login with the lab technician credentials:
Username: lab_technician Password: passwordExpected Result: You should see only one observation:
- Hemoglobin (laboratory observation with final status)
The Blood Pressure observation is not visible because the lab_technician role has the restricted scope
user/Observation.rs?category=laboratory&status=final, which only allows access to laboratory observations with final status.
This section explains the advanced Keycloak configuration that enables automatic role-to-scope mapping. Note: This is already preconfigured in the example via docker-compose.yaml and the included oidc-script-based-protocol-mapper.jar file.
The key challenge is mapping Keycloak's composite roles to SMART on FHIR scopes in the access token. Keycloak doesn't natively resolve composite roles into their constituent basic roles for the scope claim. To solve this, we use two protocol mappers:
- Script-based mapper - Resolves the user's composite role (e.g.,
physician) into basic roles (e.g.,user/Patient.rs,user/Observation.rs) and formats them as SMART scopes in the token'sscopeclaim - Hardcoded claim mapper - Adds the
atv: "2"claim to indicate SMART on FHIR V2
If you need to configure this from scratch (not required for this example):
Keycloak must be launched with the scripts feature enabled:
# In docker-compose.yaml
command: start-dev --features="scripts" --import-realmCreate a service provider configuration that registers the script-based mapper:
mkdir -p META-INF/services
echo "org.keycloak.protocol.oidc.mappers.ScriptBasedOIDCProtocolMapper" > META-INF/services/org.keycloak.protocol.ProtocolMapper
jar cvf oidc-script-based-protocol-mapper.jar META-INF/
rm -rf META-INF/This JAR file tells Keycloak to enable the ScriptBasedOIDCProtocolMapper type for protocol mappers.
Mount the JAR file into Keycloak's providers directory:
# In docker-compose.yaml
volumes:
- ./oidc-script-based-protocol-mapper.jar:/opt/keycloak/providers/oidc-script-based-protocol-mapper.jarAfter Keycloak starts, configure a protocol mapper for the my-app client:
- Mapper Type: Script Mapper
- Script: JavaScript code that resolves composite roles and formats them as SMART scopes
- Token Claim Name:
scope - Add to Access Token: Enabled
The complete mapper configuration including the JavaScript code is defined in keycloak.json and automatically imported when Keycloak starts.

