SMART on FHIR Client
A production-ready SMART on FHIR EHR launch client for Epic, built on Spring Boot 3, Java 21, and HAPI FHIR R4. Complete OAuth2 handshake, PKCE, dynamic discovery, token refresh, OIDC user profiles, and a clinical Thymeleaf UI โ all audited and tested.
๐ฏ Key Features¶
SMART App Launch v2.2
Full EHR and standalone launch modes. Dynamic /.well-known/smart-configuration discovery, PKCE S256, Epic aud param, multi-tenant ISS caching.
Epic-Ready Out of the Box
Handles every Epic-specific requirement: launch token, aud=iss, token response extras (patient, encounter, need_patient_banner).
Proactive Token Refresh
120-second buffer refresh before expiry. Handles refresh token rotation. Force-refresh on Epic 401. Clinicians never see an unexpected session expiry.
Security Hardened
Spring Security 6 filter chain. Constant-time CSRF state comparison. Log injection sanitised. Session fixation protection. CSRF Double-Submit Cookie. denyAll() route fallback.
OIDC User Profiles
Validates Epic's id_token: RS256 algorithm, issuer, audience, expiry, nonce, JWKS key presence. Extracts UserProfile with subject, name, fhirUser.
Clinical UI Included
Thymeleaf templates with patient banner, dashboard stat cards, conditions table, medications table, clinician profile. Matches Epic's UI contract for needPatientBanner.
140+ Tests
WireMock for all network calls. @WebMvcTest for controllers. Pure unit tests for all domain classes. SMART Health IT and Epic sandbox integration test profiles.
Production Architecture
Redis-ready session objects (Serializable, pinned serialVersionUID). HAPI FHIR R4 with timeout config. Actuator health probes. Structured for App Orchard submission.
๐ At a Glance¶
๐ Quick Start¶
Get the app running against the free SMART Health IT sandbox in under 5 minutes โ no Epic account required.
# Clone the repository
git clone https://github.com/AKHester-Technologies/akhester-smart-on-fhir.git
cd smart-fhir-client
# Run against the public SMART Health IT sandbox (no credentials needed)
mvn spring-boot:run -Dspring-boot.run.profiles=smart
# Open the app
open http://localhost:8080
Then go to launch.smarthealthit.org, pick a synthetic patient, and enter http://localhost:8080/launch as your launch URL.
๐๏ธ Architecture Overview¶
The app is structured around the SMART on FHIR EHR launch sequence. Each step maps to one or more Spring beans.
sequenceDiagram
participant E as Epic (EHR)
participant L as SmartLaunchController
participant D as SmartDiscoveryService
participant A as SmartAuthRequestBuilder
participant C as SmartCallbackController
participant T as SmartTokenService
participant X as SmartContextExtractor
participant F as FhirClientFactory
E->>L: GET /launch?iss=...&launch=...
L->>D: discover(iss)
D-->>L: SmartConfiguration (auth + token endpoints)
L->>A: buildAuthorizeUrl(session)
Note over A: Generates PKCE pair<br/>Stores verifier in session
L-->>E: 302 โ Epic authorize URL
E->>C: GET /callback?code=...&state=...
C->>C: Validate state (CSRF)
C->>T: exchange(code, verifier)
T-->>C: SmartTokenResponse
C->>X: extract(tokenResponse, iss)
X-->>C: SmartLaunchContext (stored in session)
C-->>E: 302 โ /
E->>F: GET /api/patient
F->>F: FhirClientFactory.create(ctx)
F-->>E: Patient JSON
๐ The SMART Launch Flow¶
Epic calls GET /launch?iss=...&launch=.... The ISS identifies the FHIR server; the launch token binds the EHR context (patient, encounter).
SmartDiscoveryService fetches {iss}/.well-known/smart-configuration to resolve the authorization and token endpoints. Cached per ISS.
PkceHelper generates a 96-byte code_verifier and S256 code_challenge. SmartAuthRequestBuilder builds the authorize URL with Epic-specific aud, launch, and PKCE params.
SmartCallbackController validates the state nonce (CSRF), retrieves the PKCE verifier, and calls SmartTokenService to POST the code exchange.
SmartContextExtractor parses Epic's token response extras (patient, encounter, need_patient_banner), validates the id_token via IdTokenValidator, and stores SmartLaunchContext in the session.
FhirClientFactory creates a HAPI R4 IGenericClient with a BearerTokenInterceptor. The UI fetches Patient, Condition, and MedicationRequest resources โ with scope gating before every call.
๐ฆ Package Structure¶
PkceParameters
SmartAuthRequestBuilder
SmartContextExtractor
SmartConfiguration
SmartDiscoveryException
BearerTokenInterceptor
PatientDataController
SmartLaunchSession
UserProfile
IdTokenException
TokenRefreshFilter
TokenRefreshException
SmartSecurityFilter
SmartAuthenticationToken
SmartTokenService
SmartTokenResponse
๐ Documentation¶
Getting Started
Introduction, prerequisites, quick start, configuration, and Epic sandbox registration.
Developer Guide
Deep dives into each layer: launch flow, FHIR client, OIDC, token refresh, and UI.
API Reference
All 8 REST endpoints with request/response schemas, scope requirements, and error codes.
Testing Guide
Running unit tests, SMART Health IT sandbox ITs, and the Epic sandbox manual checklist.
akhester-smart-on-fhir vs HealthLX/smart-on-fhir¶
| Feature | HealthLX (2019) | This Client |
|---|---|---|
| SMART spec | v1 (static YAML) | v2.2 (dynamic discovery) |
| PKCE | โ Missing | โ RFC 7636 S256 |
| Spring Boot | 2.x (Boot 3 incompatible) | 3.3.5 |
| Spring Security | 5 (WebSecurityConfigurerAdapter โ removed) |
6 (SecurityFilterChain) |
| Token refresh | โ Not implemented | โ Proactive 120s buffer |
| OIDC validation | โ Not implemented | โ RS256, JWKS, nonce |
| Standalone launch | โ Not implemented | โ Full support |
| FHIR client | โ Not included | โ HAPI R4 + bearer token |
Epic aud param |
โ Missing | โ Required by Epic |
| Test coverage | Minimal | 140+ unit tests |
| Last commit | December 2019 | 2025 |
๐ค Contributing¶
Contributions are welcome. Whether it's:
- Bug reports and issue reproduction cases
- Feature requests (Backend Services, Cerner support, R5)
- Documentation improvements
- Code contributions (tests especially welcome)
Please open an issue first to discuss significant changes.
๐ License¶
This project is open source under the MIT License. Free for personal, educational, and commercial use.