David Le
Back to Blog
UncategorizedFebruary 17, 2026·5 min read

Keycloak Integration: Enterprise SSO for InteropNimbus

Healthcare applications need proper authentication. Basic auth and API keys don't cut it when you're dealing with PHI-adjacent systems. InteropNimbus uses Keycloak for enterprise-grade SSO because:

D

David Le

Why Keycloak?

Healthcare applications need proper authentication. Basic auth and API keys don't cut it when you're dealing with PHI-adjacent systems. InteropNimbus uses Keycloak for enterprise-grade SSO because:

  • Standards-based — OAuth 2.0 and OpenID Connect
  • Self-hosted — runs alongside the rest of the infrastructure
  • Multi-tenant — one Keycloak instance serves multiple applications (FhirHub, InteropNimbus, etc.)
  • Enterprise features — LDAP/AD federation, MFA, session management

Shared Keycloak Instance

InteropNimbus shares a Keycloak instance with FhirHub. Both applications use the same realm but different clients:

export const authConfig = {
  url: import.meta.env.VITE_KEYCLOAK_URL ?? 'https://auth.interopnimbus.davidle.dev',
  realm: import.meta.env.VITE_KEYCLOAK_REALM ?? 'interopnimbus',
  clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID ?? 'interopnimbus-frontend',
} as const

The Keycloak URL, realm, and client ID are all configurable via environment variables, injected at build time through Vite.

PKCE Authentication Flow

InteropNimbus uses PKCE (Proof Key for Code Exchange) with S256, the recommended flow for public clients (SPAs):

keycloak
  .init({
    onLoad: 'check-sso',
    silentCheckSsoRedirectUri:
      `${window.location.origin}/silent-check-sso.html`,
    pkceMethod: 'S256',
    checkLoginIframe: false,
  })
  .then((authenticated) => {
    if (authenticated) { syncAuthStore(); removeSplash(); setReady(true) }
    else { keycloak.login() }
  })
  .catch(() => { keycloak.clearToken(); keycloak.login() })

Key Configuration Choices

  • onLoad: 'check-sso' — checks if the user is already authenticated via Keycloak's session, enabling seamless SSO across applications
  • silentCheckSsoRedirectUri — uses a hidden iframe for silent token checks, avoiding visible redirects
  • pkceMethod: 'S256' — uses SHA-256 for the PKCE challenge, preventing authorization code interception attacks
  • checkLoginIframe: false — disables the legacy login iframe check, which conflicts with modern CSP headers

Token Lifecycle

The AuthProvider manages the full token lifecycle:

  1. Init — check-sso to see if user is already logged in
  2. Login — redirect to Keycloak if not authenticated
  3. Sync — extract user info from the ID token and populate the Zustand auth store
  4. Refresh — automatically refresh tokens 30 seconds before expiry
  5. Logout — clear tokens and redirect back to Keycloak login
keycloak.onTokenExpired = () => {
  keycloak.updateToken(30).catch(() => {
    useAuthStore.getState().clearAuth()
    keycloak.login()
  })
}

If the token refresh fails (e.g., the session was revoked server-side), the user is redirected back to login rather than seeing a broken state.

Role-Based Access

Keycloak realm roles are mapped to the frontend auth store:

const roles = (keycloak.realmAccess?.roles ?? []) as AuthRole[]
useAuthStore.getState().setAuth({
  sub: parsed.sub ?? '',
  email: parsed.email as string,
  name: parsed.name as string,
  roles,
})

This enables role-based UI rendering — certain features or views can be gated based on the user's assigned roles in Keycloak.

Building InteropNimbus — Part 4 of 8

Related Projects

Featured

FhirHub

An open-source clinical data platform built on FHIR R4 that unifies patient demographics, vitals, labs, medications, and conditions into a single interface with SMART on FHIR authentication and role-based access.

Next.js
React
TypeScript
.NET 8
+9
Featured

InteropNimbus

A healthcare interoperability monitoring dashboard for Mirth Connect and HAPI FHIR. Provides real-time channel health, message tracing, and FHIR gateway visibility with enterprise SSO via Keycloak.

React 19
TypeScript
Vite 7
Tailwind CSS v4
+6