Files
bd-fhir-national/ops/keycloak-setup.md
2026-03-16 00:02:58 +06:00

8.7 KiB

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 rolesCreate 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)

# 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: ClientsCreate 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: Clientsfhir-admin-pipelineService 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: Clientsfhir-admin-pipelineCredentials
  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: ClientsCreate 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: Clientsfhir-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: Clientsfhir-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: Clientsfhir-vendor-{org-id}Client scopes
  2. Click the dedicated scope link (e.g., fhir-vendor-xxx-dedicated)
  3. Navigate to: MappersAdd mapperBy 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

# 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

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

curl -s -o /dev/null -w "%{http_code}" \
  https://fhir.dghs.gov.bd/fhir/Patient

# Expected: 401

Test 3 — Expired token rejected

# 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

# Get a token from a different realm (if available) or forge iss claim
# Expected: 401

Test 5 — mci-api role required

# 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

# 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 SettingsTokens for defaults, or per-client at: Clients{client}AdvancedAdvanced settings.