Skip to main content

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โ€‹

ClassRoutesDescription
SmartLaunchControllerGET /launchReceives iss + launch from Epic, runs discovery, builds PKCE, redirects to authorization
SmartCallbackControllerGET /callbackReceives code, exchanges for tokens, stores session, redirects to dashboard
UiControllerGET /dashboard, GET /patient/*Renders Thymeleaf templates with patient context
PatientDataControllerGET /api/patient, GET /api/conditions, GET /api/medications, GET /api/summaryReturns FHIR data as JSON

serviceโ€‹

ClassDescription
SmartDiscoveryServiceFetches and caches /.well-known/smart-configuration per ISS. Caffeine cache, 10-minute TTL
SmartTokenServicePOSTs to Epic's token endpoint with code + PKCE verifier. Returns TokenResponse
SmartContextExtractorExtracts patient, encounter, needPatientBanner, scope, expiry from the token response
IdTokenValidatorValidates id_token: algorithm=RS256, iss, aud, exp, nonce. Fetches JWKS from Epic
UserProfileServiceLoads the fhirUser Practitioner resource from FHIR and builds UserProfile
TokenRefreshServiceExchanges the refresh token for new tokens. Handles rotation

securityโ€‹

ClassDescription
SecurityConfigSpring Security 6 filter chain. Stateful session, CSRF, denyAll() fallback, permitAll() for /launch, /callback, /error
SmartSecurityFilterOncePerRequestFilter. Checks that SmartLaunchContext is in session for protected routes. Redirects to /launch?error if absent
TokenRefreshFilterOncePerRequestFilter on /api/**. Calls TokenRefreshService.refreshIfNeeded() before the request proceeds
PkceHelperGenerates 96-byte code verifier (Base64URL, no padding). Computes S256 challenge with SHA-256

fhirโ€‹

ClassDescription
FhirClientFactoryCreates IGenericClient for a given SmartLaunchContext. Configures connect/socket timeouts. Registers BearerTokenInterceptor
BearerTokenInterceptorHAPI 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.

ClassFieldsDescription
SmartLaunchContextaccessToken, refreshToken, patientId, encounterId, needPatientBanner, scope, expiresAt, fhirBaseUrl, userProfileComplete post-handshake context
SmartLaunchSessioniss, launchToken, pkceParameters, noncePre-handshake data stored during the authorization redirect
PkceParameterscodeVerifier, codeChallenge, statePKCE + CSRF state
UserProfilesubject, fhirUser, name, practitionerIdExtracted from id_token + OIDC

configโ€‹

ClassPrefixKey properties
SmartPropertiessmart.epicclient-id, redirect-uri, scopes, launch-url, token-url-override
FhirPropertiessmart.fhirconnect-timeout-ms, socket-timeout-ms

Next: Security Model โ†’