Skip to main content

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.

ClassResponsibility
AuthorizationServerConfigDefines two SecurityFilterChain beans (auth server + default), registers SmartTokenResponseConverter as token endpoint handler, provides OAuth2AuthorizationService, OAuth2AuthorizationConsentService, JwtDecoder, PasswordEncoder
ClinicianJPA entity โ€” login username, BCrypt password hash, display name, FHIR Practitioner ID
ClinicianRepositorySpring Data JPA โ€” findByUsername()
ClinicianUserDetailsSpring Security UserDetails wrapping Clinician. Exposes displayName and fhirUserId for IdTokenBuilder
CliniciansUserDetailsServiceUserDetailsService โ€” loads Clinician from DB, wraps in ClinicianUserDetails. Called by Spring Auth Server login flow
JpaRegisteredClientRepositoryRegisteredClientRepository implementation backed by PostgreSQL. Converts RegisteredApp โ†’ RegisteredClient with PKCE enforced
RegisteredAppJPA entity โ€” clientId, appName, redirectUri, allowedScopes (comma-separated), accessTokenTtlSeconds override
SmartScopeAuthorizationInterceptorHAPI @Interceptor โ€” validates bearer token scopes on every FHIR request. Register this on the HAPI server, not this auth server
DataInitializerCommandLineRunner โ€” 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โ€‹

ClassResponsibility
SmartDiscoveryControllerGET /.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
SmartDiscoveryProxyFilterOncePerRequestFilter โ€” 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โ€‹

ClassResponsibility
LaunchContextJPA entity โ€” token (32-byte random), patientFhirId, encounterFhirId, needPatientBanner, clientId, launchedBy, expiresAt (+5 min), used flag
LaunchContextRepositoryfindByTokenAndUsedFalse() โ€” returns empty when already used or expired
LaunchContextServicecreateLaunchToken() โ€” generates token, persists. resolveLaunchToken() โ€” validates, marks used (single-use). @Scheduled purge every 10 minutes
LaunchPortalControllerGET /portal โ€” shows patient list from HAPI. POST /portal/launch โ€” creates launch token, redirects to SMART client
LaunchTokenExceptionThrown for invalid, expired, or already-used tokens

oidc/ โ€” RSA keys, JWKS, and id_token claimsโ€‹

ClassResponsibility
RsaKeyConfigGenerates RSA-2048 key pair at startup. Exposes @Bean RSAKey and @Bean JWKSource<SecurityContext>. Used by Spring Auth Server for JWT signing
JwksControllerGET /oauth2/jwks โ€” returns the public key only via rsaKey.toPublicJWK(). Clients use this to verify JWT signatures
IdTokenBuilderBuilds 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โ€‹

ClassResponsibility
SmartTokenCustomizerOAuth2TokenCustomizer<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
SmartTokenResponseConverterAuthenticationSuccessHandler on token endpoint โ€” decodes the issued JWT, extracts SMART extras, writes them as top-level fields in the JSON response body alongside access_token