DL
Back to Blog
TechFebruary 3, 2026·5 min read

Keycloak Configuration for FHIR Applications

How FhirHub configures Keycloak as its identity provider. Realm setup, client configuration, role mapping, JWT claim transformation, and auto-imported realm exports for reproducibility.

D

David Le

Keycloak Configuration for FHIR Applications

By David Le -- Part 12 of the FhirHub Series

Keycloak is FhirHub's identity provider. It handles user authentication, token issuance, and role management. This post covers how the fhirhub realm is configured, how roles map to authorization policies, and how the .NET API transforms Keycloak's JWT claims.

Realm Setup

FhirHub uses a dedicated Keycloak realm called fhirhub. The realm is pre-configured via a JSON export that's auto-imported on first boot:

# docker-compose.yml
keycloak:
  command: start-dev --import-realm
  volumes:
    - ./keycloak/realm-export.json:/opt/keycloak/data/import/realm-export.json:ro

The realm export contains:

  • Client configurations
  • Realm roles
  • Default users (for development)
  • Protocol mappers
  • Authentication flows

Client Configuration

FhirHub registers two Keycloak clients:

fhirhub-frontend (Public Client)

The frontend client uses the Authorization Code flow with PKCE:

  • Client Type: Public (no client secret)
  • Flow: Authorization Code with PKCE (S256)
  • Redirect URIs: http://localhost:7002/*
  • Web Origins: http://localhost:7002
  • Standard Flow Enabled: Yes
  • Direct Access Grants: No (browser-based only)

Public clients can't keep secrets, so PKCE provides security by generating a unique code verifier for each authorization request.

fhirhub-backend (Confidential Client)

The backend client uses Client Credentials for server-to-server communication with Keycloak's Admin API:

  • Client Type: Confidential (has client secret)
  • Flow: Client Credentials
  • Service Account Enabled: Yes
  • Purpose: User management, session queries

The backend uses this client to:

  • List and manage users (admin panel)
  • Query user sessions
  • Invite new users

Role Hierarchy

FhirHub defines six realm roles with progressively restricted access:

admin
  └── practitioner
        └── nurse
              └── front_desk
patient (separate hierarchy)

Each role grants specific capabilities:

admin

Full system access. Can manage users, view audit logs, delete exports. Inherits all permissions from practitioner.

practitioner

Clinical read/write access. Can create patients, write conditions and medications, order labs, manage exports. Inherits from nurse.

nurse

Clinical read access plus vitals recording. Can write vitals, manage alerts, view clinical overviews. Inherits from front_desk.

front_desk

Minimal clinical access. Can read patient demographics for scheduling and check-in purposes.

patient

Self-service access. Can read own vitals, conditions, medications, and labs. Cannot see other patients' data (enforced by PatientDataAccess policy).

Role-to-Policy Mapping

The 17 authorization policies map to roles as follows:

Policyadminpractitionernursefront_deskpatient
CanReadPatientsxxxxx
CanWritePatientsxx
CanReadVitalsxxxx
CanWriteVitalsxxx
CanReadConditionsxxxx
CanWriteConditionsxx
CanReadMedicationsxxxx
CanWriteMedicationsxx
CanReadLabsxxxx
CanOrderLabsxx
CanReadClinicalOverviewsxxx
CanViewDashboardxxx
CanManageAlertsxxx
CanManageExportsxx
CanDeleteExportsx
CanManageUsersx
CanViewAuditLogsx

Claims Transformation

Keycloak embeds roles in a non-standard JWT claim path:

{
  "realm_access": {
    "roles": ["admin", "practitioner"]
  },
  "resource_access": {
    "fhirhub-frontend": {
      "roles": ["admin"]
    }
  }
}

.NET's RequireRole() expects roles as standard ClaimTypes.Role claims. The KeycloakClaimsTransformer bridges this gap:

// Registered in Program.cs
builder.Services.AddTransient<
    IClaimsTransformation,
    KeycloakClaimsTransformer>();

The transformer runs on every request after JWT validation, extracting roles from realm_access.roles and adding them as standard role claims that .NET's authorization system can evaluate.

Token Structure

A decoded FhirHub JWT contains:

ClaimExamplePurpose
isshttp://localhost:8180/realms/fhirhubToken issuer
suba1b2c3d4-...User ID
preferred_usernamedr.smithDisplay name
realm_access.roles["practitioner"]User roles
audfhirhub-frontendIntended audience
exp1706547200Expiration time
azpfhirhub-frontendAuthorized party

The API validates iss, aud, and exp on every request:

options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuer = true,
    ValidateAudience = true,
    ValidateLifetime = true,
    NameClaimType = "preferred_username",
    RoleClaimType = ClaimTypes.Role
};

Keycloak Admin API Integration

FhirHub's admin panel communicates with Keycloak's Admin REST API for user management:

// Program.cs
builder.Services.Configure<KeycloakAdminOptions>(options =>
{
    options.AdminApiBaseUrl = builder.Configuration["Keycloak:AdminApiBaseUrl"]
        ?? "http://localhost:8180";
    options.Realm = builder.Configuration["Keycloak:Realm"] ?? "fhirhub";
    options.BackendClientId = builder.Configuration["Keycloak:BackendClientId"]
        ?? "fhirhub-backend";
    options.BackendClientSecret = builder.Configuration["Keycloak:BackendClientSecret"]
        ?? "";
});
builder.Services.AddHttpClient<IKeycloakAdminService, KeycloakAdminService>();

The KeycloakAdminService uses the backend client's service account to:

  • List users in the realm
  • Create new users (invite flow)
  • Update user roles
  • Query active sessions
  • View user details

This is exposed through the UsersController in the API, protected by the CanManageUsers policy (admin only).

Development vs. Production

For development, Keycloak runs in start-dev mode which:

  • Disables HTTPS requirement
  • Enables the admin console without restrictions
  • Uses in-memory caching

For production, you'd switch to start mode and:

  • Enable HTTPS with proper certificates
  • Restrict admin console access
  • Configure external SMTP for email verification
  • Set up session clustering for high availability
  • Use stronger default passwords

What's Next

In Part 13, we'll cover HAPI FHIR server configuration -- the PostgreSQL backend, R4 version settings, CORS, and how the .NET API communicates with HAPI using the Hl7.Fhir.R4 client library.


Find the source code on GitHub Connect on LinkedIn

Related Projects

Featured

FhirHub

A healthcare data management platform built on the HL7 FHIR R4 standard, providing a comprehensive web interface for managing patient clinical data including vitals, conditions, medications, lab orders, and bulk data exports with role-based access control and full audit logging.

Next.js 16
React 19
Typescript
Tailwind CSS 4
+8