Package Structure
The application uses a feature-based package layout. Each package is cohesive โ all classes in a package collaborate on a single concern.
Root: com.akhester.smartfhirโ
com.akhester.smartfhir
โโโ SmartFhirApplication.java Spring Boot main class
โโโ controller/ HTTP entry points
โโโ service/ Business logic
โโโ security/ Spring Security + PKCE
โโโ fhir/ HAPI FHIR client
โโโ model/
โ โโโ session/ Session state objects
โ โโโ fhir/ FHIR response wrappers
โโโ config/ @ConfigurationProperties beans
controllerโ
| Class | Routes | Description |
|---|---|---|
SmartLaunchController | GET /launch | Receives iss + launch from Epic, runs discovery, builds PKCE, redirects to authorization |
SmartCallbackController | GET /callback | Receives code, exchanges for tokens, stores session, redirects to dashboard |
UiController | GET /dashboard, GET /patient/* | Renders Thymeleaf templates with patient context |
PatientDataController | GET /api/patient, GET /api/conditions, GET /api/medications, GET /api/summary | Returns FHIR data as JSON |
serviceโ
| Class | Description |
|---|---|
SmartDiscoveryService | Fetches and caches /.well-known/smart-configuration per ISS. Caffeine cache, 10-minute TTL |
SmartTokenService | POSTs to Epic's token endpoint with code + PKCE verifier. Returns TokenResponse |
SmartContextExtractor | Extracts patient, encounter, needPatientBanner, scope, expiry from the token response |
IdTokenValidator | Validates id_token: algorithm=RS256, iss, aud, exp, nonce. Fetches JWKS from Epic |
UserProfileService | Loads the fhirUser Practitioner resource from FHIR and builds UserProfile |
TokenRefreshService | Exchanges the refresh token for new tokens. Handles rotation |
securityโ
| Class | Description |
|---|---|
SecurityConfig | Spring Security 6 filter chain. Stateful session, CSRF, denyAll() fallback, permitAll() for /launch, /callback, /error |
SmartSecurityFilter | OncePerRequestFilter. Checks that SmartLaunchContext is in session for protected routes. Redirects to /launch?error if absent |
TokenRefreshFilter | OncePerRequestFilter on /api/**. Calls TokenRefreshService.refreshIfNeeded() before the request proceeds |
PkceHelper | Generates 96-byte code verifier (Base64URL, no padding). Computes S256 challenge with SHA-256 |
fhirโ
| Class | Description |
|---|---|
FhirClientFactory | Creates IGenericClient for a given SmartLaunchContext. Configures connect/socket timeouts. Registers BearerTokenInterceptor |
BearerTokenInterceptor | HAPI IClientInterceptor. Adds Authorization: Bearer {token} to every outgoing request |
model.sessionโ
These objects are stored in the HTTP session. All implement Serializable with pinned serialVersionUID โ required for Redis-based session replication.
| Class | Fields | Description |
|---|---|---|
SmartLaunchContext | accessToken, refreshToken, patientId, encounterId, needPatientBanner, scope, expiresAt, fhirBaseUrl, userProfile | Complete post-handshake context |
SmartLaunchSession | iss, launchToken, pkceParameters, nonce | Pre-handshake data stored during the authorization redirect |
PkceParameters | codeVerifier, codeChallenge, state | PKCE + CSRF state |
UserProfile | subject, fhirUser, name, practitionerId | Extracted from id_token + OIDC |
configโ
| Class | Prefix | Key properties |
|---|---|---|
SmartProperties | smart.epic | client-id, redirect-uri, scopes, launch-url, token-url-override |
FhirProperties | smart.fhir | connect-timeout-ms, socket-timeout-ms |
Next: Security Model โ