# Keycloak Setup — BD FHIR National **Realm:** `hris` **Keycloak URL:** `https://auth.dghs.gov.bd` **Audience:** DGHS Identity and Access Management team --- ## Overview This document covers the Keycloak configuration required for BD FHIR National deployment. It assumes the `hris` realm and `mci-api` role already exist (pre-existing national HRIS configuration). Only the additions for FHIR deployment are documented here. --- ## Part 1 — Create `fhir-admin` realm role The `fhir-admin` role grants access to: - `DELETE /admin/terminology/cache` — terminology cache flush - `GET /admin/terminology/cache/stats` — cache statistics This role is **not** assigned to vendor clients. It is assigned only to the ICD-11 version upgrade pipeline service account and DGHS system administrators. ### Steps (Keycloak Admin Console) 1. Log in to `https://auth.dghs.gov.bd/admin/master/console` 2. Select realm: **hris** 3. Navigate to: **Realm roles** → **Create role** 4. Fill in: - **Role name:** `fhir-admin` - **Description:** `BD FHIR server administrative operations — cache management and system configuration` 5. Click **Save** ### Steps (Keycloak Admin REST API — for automation) ```bash # Get admin token ADMIN_TOKEN=$(curl -s -X POST \ "https://auth.dghs.gov.bd/realms/master/protocol/openid-connect/token" \ -d "grant_type=password" \ -d "client_id=admin-cli" \ -d "username=${KEYCLOAK_ADMIN_USER}" \ -d "password=${KEYCLOAK_ADMIN_PASSWORD}" \ | jq -r '.access_token') # Create fhir-admin role curl -s -X POST \ "https://auth.dghs.gov.bd/admin/realms/hris/roles" \ -H "Authorization: Bearer ${ADMIN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "name": "fhir-admin", "description": "BD FHIR server administrative operations" }' ``` --- ## Part 2 — Create `fhir-admin` service account client The version upgrade pipeline authenticates with a dedicated client. This client must never be shared with vendor systems. ### Steps (Admin Console) 1. Navigate to: **Clients** → **Create client** 2. **Client type:** OpenID Connect 3. **Client ID:** `fhir-admin-pipeline` 4. Click **Next** 5. **Client authentication:** ON (confidential client) 6. **Service accounts roles:** ON 7. **Standard flow:** OFF (machine-to-machine only) 8. Click **Save** ### Assign fhir-admin role to service account 1. Navigate to: **Clients** → `fhir-admin-pipeline` → **Service accounts roles** 2. Click **Assign role** 3. Filter by: **Filter by realm roles** 4. Select: `fhir-admin` 5. Click **Assign** ### Retrieve client secret 1. Navigate to: **Clients** → `fhir-admin-pipeline` → **Credentials** 2. Copy **Client secret** — store in your secrets vault 3. This secret is used in `ops/version-upgrade-integration.md` --- ## Part 3 — Configure vendor clients Each vendor organisation requires one Keycloak client. This section documents the **template** for creating a vendor client. Repeat for each vendor. ### Naming convention ``` fhir-vendor-{organisation-id} ``` Where `{organisation-id}` is the DGHS facility code, e.g.: - `fhir-vendor-DGHS-FAC-001` for Dhaka Medical College Hospital - `fhir-vendor-DGHS-FAC-002` for Square Hospital ### Steps (Admin Console) 1. Navigate to: **Clients** → **Create client** 2. **Client type:** OpenID Connect 3. **Client ID:** `fhir-vendor-{organisation-id}` 4. Click **Next** 5. **Client authentication:** ON 6. **Service accounts roles:** ON 7. **Standard flow:** OFF 8. Click **Save** ### Assign mci-api role 1. Navigate to: **Clients** → `fhir-vendor-{org-id}` → **Service accounts roles** 2. Click **Assign role** 3. Select: `mci-api` 4. Click **Assign** ### Add sending_facility user attribute The `sending_facility` claim is a custom token mapper that injects the vendor's DGHS facility code into every token issued to this client. The `KeycloakJwtInterceptor` reads this claim for audit logging. **Without this mapper, audit logs will show `client_id` as the facility identifier instead of the DGHS facility code. This degrades audit quality and generates WARN logs in HAPI on every submission.** #### Create user attribute on service account 1. Navigate to: **Clients** → `fhir-vendor-{org-id}` → **Service accounts** 2. Click the service account user link (e.g., `service-account-fhir-vendor-xxx`) 3. Navigate to: **Attributes** tab 4. Click **Add attribute** 5. Key: `sending_facility` 6. Value: `{DGHS facility code}` (e.g., `DGHS-FAC-001`) 7. Click **Save** #### Create token mapper 1. Navigate to: **Clients** → `fhir-vendor-{org-id}` → **Client scopes** 2. Click the dedicated scope link (e.g., `fhir-vendor-xxx-dedicated`) 3. Navigate to: **Mappers** → **Add mapper** → **By configuration** 4. Select: **User Attribute** 5. Fill in: - **Name:** `sending-facility-mapper` - **User Attribute:** `sending_facility` - **Token Claim Name:** `sending_facility` - **Claim JSON Type:** String - **Add to access token:** ON - **Add to ID token:** OFF - **Add to userinfo:** OFF 6. Click **Save** #### Verify token contains sending_facility ```bash # Get vendor token TOKEN=$(curl -s -X POST \ "https://auth.dghs.gov.bd/realms/hris/protocol/openid-connect/token" \ -d "grant_type=client_credentials" \ -d "client_id=fhir-vendor-{org-id}" \ -d "client_secret={secret}" \ | jq -r '.access_token') # Decode and check claims (base64 decode middle segment) echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq '{ iss, sub, azp, exp, sending_facility, realm_access: .realm_access.roles }' # Expected output: # { # "iss": "https://auth.dghs.gov.bd/realms/hris", # "sub": "...", # "azp": "fhir-vendor-{org-id}", # "exp": ..., # "sending_facility": "DGHS-FAC-001", # "realm_access": ["mci-api", "offline_access"] # } ``` --- ## Part 4 — Token validation verification After creating a client, verify the full token validation chain works before onboarding the vendor. ### Test 1 — Valid token accepted ```bash TOKEN=$(curl -s -X POST \ "https://auth.dghs.gov.bd/realms/hris/protocol/openid-connect/token" \ -d "grant_type=client_credentials" \ -d "client_id=fhir-vendor-{org-id}" \ -d "client_secret={secret}" \ | jq -r '.access_token') curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: Bearer ${TOKEN}" \ https://fhir.dghs.gov.bd/fhir/Patient # Expected: 200 (empty bundle) or 404 — NOT 401 ``` ### Test 2 — Missing token rejected ```bash curl -s -o /dev/null -w "%{http_code}" \ https://fhir.dghs.gov.bd/fhir/Patient # Expected: 401 ``` ### Test 3 — Expired token rejected ```bash # Use a deliberately expired token (exp in the past) # Easiest: wait for a token to expire (default Keycloak token lifetime: 5 minutes) # Then attempt a request with the expired token. # Expected: 401 ``` ### Test 4 — Wrong realm rejected ```bash # Get a token from a different realm (if available) or forge iss claim # Expected: 401 ``` ### Test 5 — mci-api role required ```bash # Create a test client WITHOUT mci-api role # Get token for that client # Attempt FHIR request # Expected: 401 ``` ### Test 6 — fhir-admin endpoint requires fhir-admin role ```bash # Use a vendor token (mci-api only, no fhir-admin) VENDOR_TOKEN=... curl -s -w "\n%{http_code}" \ -X DELETE \ -H "Authorization: Bearer ${VENDOR_TOKEN}" \ https://fhir.dghs.gov.bd/admin/terminology/cache # Expected: 403 # Use fhir-admin token ADMIN_TOKEN=$(curl -s -X POST \ "https://auth.dghs.gov.bd/realms/hris/protocol/openid-connect/token" \ -d "grant_type=client_credentials" \ -d "client_id=fhir-admin-pipeline" \ -d "client_secret={admin_secret}" \ | jq -r '.access_token') curl -s -w "\n%{http_code}" \ -X DELETE \ -H "Authorization: Bearer ${ADMIN_TOKEN}" \ https://fhir.dghs.gov.bd/admin/terminology/cache # Expected: 200 with flush summary JSON ``` --- ## Part 5 — Token lifetime configuration Keycloak default access token lifetime is 5 minutes. For machine-to-machine FHIR submissions, this is appropriate — vendor systems must refresh tokens before expiry. Do not increase the token lifetime to accommodate vendors who are not refreshing tokens correctly. Token refresh is the vendor's responsibility, not a server-side workaround. **Recommended settings for vendor clients:** | Setting | Value | Rationale | |---------|-------|-----------| | Access Token Lifespan | 5 minutes | Short-lived — minimises window for token replay | | Refresh Token Max Reuse | 0 | One-time use refresh tokens | | Client Session Idle | 30 minutes | Vendor batch jobs may pause between submissions | | Client Session Max | 8 hours | Maximum session for a single batch run | Configure at: **Realm Settings** → **Tokens** for defaults, or per-client at: **Clients** → `{client}` → **Advanced** → **Advanced settings**.