Introduction
In a previous post, I wrote about how to use Keycloak and the APISIX openid_connect
plugin to add support for SMART on FHIR to HAPI FHIR.
In this post we are going to look at how to use Keycloak and the APISIX authz-keycloak
plugin to add support for SMART on FHIR to HAPI FHIR.
Keycloak Authorization Services
Fine-grained Authorisation
You must allow the 'Authorization' capability config setting in order to enable support for fine-grained authorisation.
APISIX
AuthZ Keycloak plugin
The authz-keycloak
plugin enables APISIX to leverage the fine-grained authorisation policies and access control mechanisms supported by Keycloak.
See: Keycloak - Authorization Services Guide
Static permissions
The authz-keycloak
plugin can be configured to support different authorisation scenarios.
For example, you can configure Keycloak to use scope-based permissions associated with a client scope policy and configure the authz-keycloak
plugin to use static permissions:
- Resource: Patient
- Authorisation Scope: Patient.read
- Client Scope: system/Patient.read
- Client Scope Policy: patient-read-client-scope-policy
- Scope-based Permission: patient-read-scope-permission
permissions: [ "Patient#Patient.read" ]
Create an Authorisation Scope in Keycloak
Create an Authorisation Scope (Patient.read) in Keycloak:
Create a Resource in Keycloak
Create a Resource (Patient) in Keycloak:
Create a Client Scope in Keycloak
Create a Client Scope (system/Patient.read) in Keycloak:
Create a Client Scope Policy in Keycloak
Create a Client Scope Policy (patient-read-client-scope-policy) in Keycloak:
Create a Scope-based Permission in Keycloak
Create a Scope-based Permission (patient-read-scope-permission) in Keycloak:
Configure APISIX
Create a route as follows:
...
- name: hapi-fhir-api
host: hapi-fhir.au.localhost
uri: /fhir/Patient*
methods: [ "GET" ]
upstream_id: 1
plugins:
authz-keycloak:
lazy_load_paths: false
ssl_verify: true
client_id: ${{CLIENT_ID}}
client_secret: ${{CLIENT_SECRET}}
discovery: ${{PROTOCOL}}://${{KEYCLOAK_HOSTNAME}}:${{KEYCLOAK_PORT}}/realms/${{KEYCLOAK_REALM}}/.well-known/openid-configuration
permissions: [ "Patient#Patient.read" ]
Each entry in the 'permissions' attribute must be formatted as expected by the token endpoint's permission parameter.
For example: "Resource#Authorisation Scope".
See: Keycloak - Authorization Services Guide: Obtaining permissions
Call the HAPI FHIR API
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"
Note: You can use jwt.io to decode the access token.
Introspect a token
To introspect an Access Token you will need to POST to the introspect URL.
For example:
curl -X POST "https://keycloak.au.localhost:8443/realms/hapi-fhir-dev/protocol/openid-connect/token/introspect" \
-H 'content-type: application/x-www-form-urlencoded' \
-d client_id=oauth2-proxy \
-d client_secret=aHkRec1BYkfaKgMg164JmvKu8u9iWNHM \
-d "token_type_hint=access_token&token=$ACCESS_TOKEN"
Call the API
To call the HAPI FHIR API, an application must pass the access token as a Bearer token in the Authorization header of the 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"
You should see something like:
{
"resourceType": "Bundle",
"id": "9d80c83a-0b06-4b78-bce2-21e6666348d8",
"meta": {
"lastUpdated": "2025-05-23T05:43:01.959+00:00"
},
"type": "searchset",
"total": 1,
"link": [ {
"relation": "self",
"url": "https://hapi-fhir.au.localhost/fhir/Patient?_id=baratz-toni"
} ],
"entry": [ {
"fullUrl": "https://hapi-fhir.au.localhost/fhir/Patient/baratz-toni",
"resource": {
"resourceType": "Patient",
"id": "baratz-toni",
"meta": {
"versionId": "1",
"lastUpdated": "2025-05-23T05:42:36.551+00:00",
"source": "#rKTCeZfmnReSjm8X",
"profile": [ "http://hl7.org.au/fhir/core/StructureDefinition/au-core-patient" ]
},
"extension": [ {
"url": "http://hl7.org.au/fhir/StructureDefinition/indigenous-status",
"valueCoding": {
"system": "https://healthterminologies.gov.au/fhir/CodeSystem/australian-indigenous-status-1",
"code": "1",
"display": "Aboriginal but not Torres Strait Islander origin"
}
}, {
"url": "http://hl7.org/fhir/StructureDefinition/individual-genderIdentity",
"extension": [ {
"url": "value",
"valueCodeableConcept": {
"coding": [ {
"system": "http://snomed.info/sct",
"code": "446141000124107",
"display": "Identifies as female gender"
} ]
}
} ]
}, {
"url": "http://hl7.org/fhir/StructureDefinition/individual-pronouns",
"extension": [ {
"url": "value",
"valueCodeableConcept": {
"coding": [ {
"system": "http://loinc.org",
"code": "LA29519-8",
"display": "she/her/her/hers/herself"
} ]
}
} ]
}, {
"url": "http://hl7.org/fhir/StructureDefinition/individual-recordedSexOrGender",
"extension": [ {
"url": "type",
"valueCodeableConcept": {
"coding": [ {
"system": "http://snomed.info/sct",
"code": "1515311000168102",
"display": "Biological sex at birth"
} ]
}
}, {
"url": "value",
"valueCodeableConcept": {
"coding": [ {
"system": "http://snomed.info/sct",
"code": "248152002",
"display": "Female"
} ]
}
} ]
} ],
"identifier": [ {
"extension": [ {
"url": "http://hl7.org.au/fhir/StructureDefinition/ihi-status",
"valueCoding": {
"system": "https://healthterminologies.gov.au/fhir/CodeSystem/ihi-status-1",
"code": "active"
}
}, {
"url": "http://hl7.org.au/fhir/StructureDefinition/ihi-record-status",
"valueCoding": {
"system": "https://healthterminologies.gov.au/fhir/CodeSystem/ihi-record-status-1",
"code": "verified",
"display": "verified"
}
} ],
"type": {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "NI"
} ],
"text": "IHI"
},
"system": "http://ns.electronichealth.net.au/id/hi/ihi/1.0",
"value": "8003608000311662"
}, {
"type": {
"coding": [ {
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MC"
} ],
"text": "Medicare Number"
},
"system": "http://ns.electronichealth.net.au/id/medicare-number",
"value": "69518252411"
} ],
"name": [ {
"use": "official",
"family": "BARATZ",
"given": [ "Toni" ]
} ],
"telecom": [ {
"system": "phone",
"value": "0870101270",
"use": "home"
}, {
"system": "phone",
"value": "0491570156",
"use": "mobile"
}, {
"system": "phone",
"value": "0870108006",
"use": "work"
} ],
"gender": "female",
"birthDate": "1978-06-16",
"address": [ {
"line": [ "24 Law Cir" ],
"city": "Bassendean",
"state": "WA",
"postalCode": "6054",
"country": "AU"
} ]
},
"search": {
"mode": "match"
}
} ]
}
Test Data
See: HAPI FHIR and AU Core Test Data
Source Code
References
System Hardening
- Australian Signals Directorate: Implementing Certificates, TLS, HTTPS and Opportunistic TLS
- Cloudflare docs: Cipher suites recommendations
HL7
- HL7: Implementation Guide
- HL7: FHIR NPM Packages
- AU Core: Publication (Version) History
- AU Core FHIR Implementation Guide: AU Core - 1.0.0-preview
- AU Core FHIR Implementation Guide: Testing FAQs
- Sparked AU Core Test Data: Postman collection
SMART on FHIR
- HL7: SMART App Launch
- SMART Health IT: SMART on FHIR
SMART on FHIR - Standalone Launch
- Project Alvearie: SMART App Launch
- Project Alvearie: Keycloak extensions for FHIR
- Keycloak extensions for FHIR: Upgrade to the Quarkus-based distribution
- Keycloak discussion: Fine grained scope consent management
SMART on FHIR - EHR Launch
Keycloak
- Keycloak docs: Configuring Keycloak for production
- Keycloak docs: Configuring TLS
- Keycloak docs: Configuring trusted certificates
- Keycloak docs: Configuring the hostname
- Keycloak docs: Using a reverse proxy
- Keycloak docs: Running Keycloak in a container
- Keycloak docs: Migrating to the Quarkus distribution
- Keycloak docs: Upgrading Guide - 26.1.0
- Keycloak docs: Authorization Services Guide
Keycloak-based Development
- GitHub: Keycloak Project Example
- GitHub: Awesome Keycloak
Keycloak Support
- Google Group: Keycloak User
- Google Group: Keycloak Dev
APISIX
- APISIX docs: Deployment modes
- APISIX docs: SSL Protocol
- APISIX docs: Certificate
- APISIX docs: Plugins - OpenID Connect
HAPI FHIR
- HAPI FHIR: Website
- HAPI FHIR: Documentation
- Google Group: HAPI FHIR