SMART on FHIR Authorization Server
The authorization layer that sits between your SMART client apps and a HAPI FHIR JPA server. Implements the full SMART App Launch v2.2 authentication protocol โ PKCE, launch tokens, OIDC id_token, scope enforcement โ without Epic.
What this server does¶
graph LR
C["โ SMART Client<br/>akhester-smart-on-fhir<br/>:8080"]
A["๐ Auth Server<br/>this project<br/>:9000"]
F["๐ฆ HAPI FHIR JPA<br/>patient data<br/>:8080"]
C -->|"/.well-known/smart-configuration"| A
C -->|"GET /oauth2/authorize"| A
C -->|"POST /oauth2/token"| A
A -->|"access_token + patient + encounter + id_token"| C
C -->|"Bearer token FHIR calls"| F
A -.->|"scope enforcement interceptor"| F
The auth server handles everything between the SMART client's discovery request and its first authenticated FHIR call. The HAPI FHIR JPA server handles data.
Features¶
PKCE Enforced
S256 required on all clients via requireProofKey(true). Spring Authorization Server handles RFC 7636 verification automatically.
EHR Launch Context
Single-use launch tokens bind a session to a patient and encounter. 5-minute expiry. Scheduled purge of stale tokens.
SMART Token Extras
patient, encounter, need_patient_banner returned as top-level fields in the token response body โ exactly what the SMART client reads.
OIDC id_token
RS256-signed with clinician name and fhirUser claims. JWKS endpoint for signature verification. Works with the client's IdTokenValidator.
Scope Enforcement
HAPI interceptor validates patient/Patient.rs, patient/Condition.rs etc. on every FHIR request. Wildcard patient/*.rs supported.
JPA App Registry
Registered SMART apps stored in PostgreSQL. Add or disable apps at runtime without restarting. Seeded on first startup by DataInitializer.
Patient Picker Portal
Clinician login page + patient selection UI. Fetches patients from HAPI, generates a launch token, redirects to the SMART client.
Dev Profile with H2
Runs in one command with zero setup โ no Docker, no PostgreSQL. Switch to PostgreSQL with three environment variables.
At a glance¶
The launch flow¶
The clinician opens http://localhost:9000/portal, logs in with their credentials, and selects a patient from the HAPI FHIR server. Clicks Launch App.
LaunchContextService generates a 32-byte random opaque token and stores it in PostgreSQL bound to the patient FHIR ID. Single-use, 5-minute expiry.
The server redirects the browser to http://localhost:8080/launch?iss={fhirBaseUrl}&launch={token}. The SMART client's SmartLaunchController handles this.
The client fetches {iss}/.well-known/smart-configuration from the FHIR server. SmartDiscoveryProxyFilter forwards this to http://localhost:9000.
Client generates S256 PKCE pair, stores verifier in session, redirects to /oauth2/authorize with launch={token}, code_challenge, and all SMART scopes.
SmartTokenCustomizer resolves the launch token to patient/encounter. SmartTokenResponseConverter writes them as top-level fields in the JSON response alongside access_token.
Port layout¶
| Port | Service | What runs there |
|---|---|---|
:9000 |
This auth server | /oauth2/authorize, /oauth2/token, /oauth2/jwks, /.well-known/smart-configuration, /portal |
:8080 |
HAPI FHIR JPA server | /fhir/Patient, /fhir/Condition, /fhir/MedicationRequest, etc. |
:8080 |
SMART client (dev) | /launch, /callback, /api/*, / |
Ports in production
In production, all three services are typically on port 443 behind a reverse proxy,
each on a different hostname or path prefix.
Related project¶
This server is designed to pair with akhester-smart-on-fhir โ the SMART client that talks to this server. The two projects together form a complete SMART on FHIR platform without Epic.
Connect the client โ Quick Start โ
License¶
MIT โ free for personal, educational, and commercial use.