Package Structure
com.ajfhir.auth/
โโโ SmartFhirServerApplication Entry point + @EnableScheduling
โโโ SmartServerProperties @ConfigurationProperties(prefix="smart.server")
โ
โโโ auth/ Spring Security + registered apps + user store
โโโ discovery/ SMART discovery document + proxy filter
โโโ launch/ EHR launch tokens
โโโ oidc/ RSA keys + JWKS + id_token claims
โโโ token/ SMART token customization
โโโ (test) Unit + component tests
auth/ โ Authorization server and user storeโ
The largest package. Contains Spring Security configuration, clinician credentials, and registered app management.
| Class | Responsibility |
|---|---|
AuthorizationServerConfig | Defines two SecurityFilterChain beans (auth server + default), registers SmartTokenResponseConverter as token endpoint handler, provides OAuth2AuthorizationService, OAuth2AuthorizationConsentService, JwtDecoder, PasswordEncoder |
Clinician | JPA entity โ login username, BCrypt password hash, display name, FHIR Practitioner ID |
ClinicianRepository | Spring Data JPA โ findByUsername() |
ClinicianUserDetails | Spring Security UserDetails wrapping Clinician. Exposes displayName and fhirUserId for IdTokenBuilder |
CliniciansUserDetailsService | UserDetailsService โ loads Clinician from DB, wraps in ClinicianUserDetails. Called by Spring Auth Server login flow |
JpaRegisteredClientRepository | RegisteredClientRepository implementation backed by PostgreSQL. Converts RegisteredApp โ RegisteredClient with PKCE enforced |
RegisteredApp | JPA entity โ clientId, appName, redirectUri, allowedScopes (comma-separated), accessTokenTtlSeconds override |
SmartScopeAuthorizationInterceptor | HAPI @Interceptor โ validates bearer token scopes on every FHIR request. Register this on the HAPI server, not this auth server |
DataInitializer | CommandLineRunner โ seeds dr.smith, dr.jones, and ajfhir-smart-clientjfhir-smart-client` on first startup if DB is empty |
Dependency direction: auth/ depends on token/ (for SmartTokenResponseConverter) and oidc/ (for RSAKey).
discovery/ โ SMART discovery and proxyโ
| Class | Responsibility |
|---|---|
SmartDiscoveryController | GET /.well-known/smart-configuration โ returns the exact fields our SMART client expects: authorization_endpoint, token_endpoint, capabilities (including launch-ehr), code_challenge_methods_supported: [S256], jwks_uri |
SmartDiscoveryProxyFilter | OncePerRequestFilter โ proxies /.well-known/smart-configuration requests to this auth server. Register this on the HAPI FHIR JPA server so clients reaching port 8080 get the discovery document |
launch/ โ EHR launch context tokensโ
| Class | Responsibility |
|---|---|
LaunchContext | JPA entity โ token (32-byte random), patientFhirId, encounterFhirId, needPatientBanner, clientId, launchedBy, expiresAt (+5 min), used flag |
LaunchContextRepository | findByTokenAndUsedFalse() โ returns empty when already used or expired |
LaunchContextService | createLaunchToken() โ generates token, persists. resolveLaunchToken() โ validates, marks used (single-use). @Scheduled purge every 10 minutes |
LaunchPortalController | GET /portal โ shows patient list from HAPI. POST /portal/launch โ creates launch token, redirects to SMART client |
LaunchTokenException | Thrown for invalid, expired, or already-used tokens |
oidc/ โ RSA keys, JWKS, and id_token claimsโ
| Class | Responsibility |
|---|---|
RsaKeyConfig | Generates RSA-2048 key pair at startup. Exposes @Bean RSAKey and @Bean JWKSource<SecurityContext>. Used by Spring Auth Server for JWT signing |
JwksController | GET /oauth2/jwks โ returns the public key only via rsaKey.toPublicJWK(). Clients use this to verify JWT signatures |
IdTokenBuilder | Builds OIDC claims map for id_token โ loads ClinicianUserDetails for the authenticated username, extracts displayName (โ name claim) and fhirUserId (โ fhirUser claim) |
Key persistence
The RSA key pair is regenerated on every server restart. Tokens issued before a restart cannot be verified after it. For production, load the key from a keystore or secrets manager. See Deployment.
token/ โ SMART token customizationโ
| Class | Responsibility |
|---|---|
SmartTokenCustomizer | OAuth2TokenCustomizer<JwtEncodingContext> โ for access_token: reads launch param from stored OAuth2AuthorizationRequest, resolves launch token, adds patient/encounter/need_patient_banner JWT claims. For id_token: calls IdTokenBuilder |
SmartTokenResponseConverter | AuthenticationSuccessHandler on token endpoint โ decodes the issued JWT, extracts SMART extras, writes them as top-level fields in the JSON response body alongside access_token |