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.

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.

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:

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
SMART on FHIR
Keycloak
FHIR Info Gateway
Clinical Information Systems