Add AuthZ to HAPI FHIR - Part 2
Introduction
This is the second post, in a series of posts about adding support for (fine grained) Authorization (AuthZ) in HAPI FHIR.
- Add AuthZ to HAPI FHIR - Part 1
- Add AuthZ to HAPI FHIR - Part 2
In the first post, we identified some options to enable Authorization in HAPI FHIR.
In this post we are going to look at Option 1 in more detail.
Option 1
There are three components to this solution: an Identity Provider that authenticates the user; an Authorization Server that decides what a given user can access; and an Access Gateway which enforces those permissions.
- Identity Provider: Keycloak
- Authorization Server: Keycloak
- Access Gateway: FHIR Info Gateway
Keycloak
SMART on FHIR Scopes
SMART on FHIR defines OAuth 2.0 scopes that allow client applications to request a specific set of access rights. The client conveys this information to the authorization server in the form of a 'scope' request parameter.
The SMART on FHIR specification defines the structure of scopes, for example:
user/CarePlan.read
patient/MedicationOrder.read
system/Observation.write
To enable a user to read all the values from the CarePlan resource the client would include the user/CarePlan.read
scope in its request to the authorization server.
A resource context prefixes each SMART on FHIR scope:
user
patient
system
This value represents one of three possible scenarios: user
access to the resource is constrained by the user's access permissions; patient
access to the resource is restricted to the in-context patient; system
access is confined to system-based authorization workflows.
The following modification rights are defined:
read
write
Where read
includes "search" and "history”. And, write
includes create", "update and "delete".
Keycloak does not support wildcard scopes, clients must explicitly request each scope they require.
Also see:
- Oracle Health Millennium Platform: Scopes
- okta forum: SMART on FHIR wildcard scopes
Client Scopes
In Keycloak, an OAuth 2.0 scope is mapped to a client scope.
Navigate to the Keycloak Admin Console:
https://keycloak.au.localhost:8443
In the hapi-fhir-dev
realm, select 'Client scopes', then click the 'Create client scope' button:
Enter the details for the patient/AllergyIntolerance.read
scope:
Then click the 'Save' button.
Select 'Clients' and oauth2-proxy
and then choose the 'Client scopes' tab, click the 'Add client scope' button:
Add the patient/AllergyIntolerance.read
client scope to the client:
Note: The patient/AllergyIntolerance.read
scope is an optional scope.
If we enable the 'Consent required' setting:
An add the patient/AllergyIntolerance.read
client scope to the scope request parameter (via the project's .env
file):
...
SCOPE="openid patient/AllergyIntolerance.read"
See: .env
Then we can check that we have successfully configured the patient/AllergyIntolerance.read
client scope:
Call the FHIR API
OAuth 2.0 Client Credentials Grant
You must allow the 'Service account roles' capability config setting in order to enable support for the OAuth 2.0 Client Credentials Grant:
Request a token
To access the API, you must request an access token. You will need to POST to the token URL.
For example (scope=system/Patient.read
):
ACCESS_TOKEN=$(curl -s -X POST https://keycloak.au.localhost:8443/realms/hapi-fhir-dev/protocol/openid-connect/token \
-H 'content-type: application/x-www-form-urlencoded' \
-d grant_type=client_credentials \
-d client_id=oauth2-proxy \
-d client_secret=aHkRec1BYkfaKgMg164JmvKu8u9iWNHM \
-d scope=system/Patient.read | (jq -r '.access_token'))
# echo "$ACCESS_TOKEN"
If you decode the access token you should see something like:
{
"exp": 1737495977,
"iat": 1737495677,
"jti": "d6ba5750-c49a-419e-9d7a-80bb8eda8f5e",
"iss": "https://keycloak.au.localhost:8443/realms/hapi-fhir-dev",
"aud": [
"oauth2-proxy",
"account"
],
"sub": "da4f0912-1cd9-432d-9c9d-a55db11093de",
"typ": "Bearer",
"azp": "oauth2-proxy",
"acr": "1",
"allowed-origins": [
"*"
],
"realm_access": {
"roles": [
"default-roles-hapi-fhir-dev",
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"oauth2-proxy": {
"roles": [
"uma_protection"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "system/Patient.read profile email",
"email_verified": false,
"clientHost": "172.18.0.1",
"preferred_username": "service-account-oauth2-proxy",
"clientAddress": "172.18.0.1",
"client_id": "oauth2-proxy"
}
Call the API
To call the API, an application must pass the access token as a Bearer token in the Authorization header of your HTTP request.
For example:
curl -X GET https://hapi-fhir.au.localhost/fhir/Patient?_id=baratz-toni \
-H 'Content-Type: application/fhir+json' \
-H "Authorization: Bearer $ACCESS_TOKEN"
Source Code
References
OAuth 2.0
- IETF: OAuth 2.0 for Browser-Based Applications
- Spring docs: Implementation Guidelines for Browser-Based Applications
SMART on FHIR
- HL7: SMART App Launch
- HL7: Backend Services
- SMART Health IT: SMART on FHIR
Keycloak
- Keycloak docs: Migrating to the Quarkus distribution
- Keycloak docs: Upgrading Guide - 26.1.0
- Keycloak docs: Server Administration Guide
- Google Group: Keycloak Dev
- Google Group: Keycloak User
FHIR Info Gateway
- GitHub: FHIR Info Gateway
Clinical Information Systems
- Oracle Health Millennium Platform: Authorization Framework