304 lines
8.7 KiB
Markdown
304 lines
8.7 KiB
Markdown
# 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**.
|