Session Lifecycle
Session objects and when they are createdโ
/launch request
โ
โโ SmartLaunchSession created (iss, launchToken)
โโ PkceParameters generated (codeVerifier, codeChallenge, state, nonce)
โโ Both stored in HttpSession
/callback request
โ
โโ PkceParameters read from session (codeVerifier, state)
โโ State verified (constant-time)
โโ Token exchange (code + verifier โ access_token, refresh_token)
โโ id_token validated
โโ SmartLaunchContext built (patientId, encounterId, tokens, expiry)
โโ SmartLaunchContext stored in HttpSession
/dashboard, /api/** requests
โ
โโ SmartSecurityFilter: reads SmartLaunchContext from session
โโ TokenRefreshFilter: refreshes if < 120s remaining
โโ Controllers: use SmartLaunchContext to build FhirClient
SmartLaunchContext fieldsโ
| Field | Type | Source |
|---|---|---|
accessToken | String | Token response access_token |
refreshToken | String | Token response refresh_token |
patientId | String | Token response patient extra |
encounterId | String | Token response encounter extra (may be null) |
needPatientBanner | boolean | Token response need_patient_banner extra |
scope | Set<String> | Token response scope (split by space) |
expiresAt | Instant | now() + expires_in seconds |
fhirBaseUrl | String | iss from the launch request |
userProfile | UserProfile | Extracted from id_token claims |
Redis readinessโ
All session objects implement java.io.Serializable with pinned serialVersionUID:
public class SmartLaunchContext implements Serializable {
private static final long serialVersionUID = 1L;
// ...
}
public class PkceParameters implements Serializable {
private static final long serialVersionUID = 1L;
// ...
}
To enable Redis session storage, add to application.yml:
spring:
session:
store-type: redis
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
And add to pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
With Redis enabled, multiple application instances share session state โ a clinician's session survives a pod restart or load-balancer redirect to a different instance.
Token refresh timingโ
Token issued
โ
โโโโโโโโโโโ expires_in (e.g. 3600 s) โโโโโโโโโโบโ
โ โ Token expires
โ โโโโ 120 s โโโโโโโบโ
โ โ โ
โ โ TokenRefreshFilter fires here
โ โ (on next /api/** request)
TokenRefreshService.refreshIfNeeded():
public void refreshIfNeeded(SmartLaunchContext ctx) {
Instant refreshThreshold = ctx.getExpiresAt()
.minusSeconds(120);
if (Instant.now().isAfter(refreshThreshold)) {
refresh(ctx);
}
}
If the refresh token itself is expired, the user is redirected to /launch?error=session_expired.
โ Security Model ยท Overview