Skip to main content

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

FieldTypeSource
accessTokenStringToken response access_token
refreshTokenStringToken response refresh_token
patientIdStringToken response patient extra
encounterIdStringToken response encounter extra (may be null)
needPatientBannerbooleanToken response need_patient_banner extra
scopeSet<String>Token response scope (split by space)
expiresAtInstantnow() + expires_in seconds
fhirBaseUrlStringiss from the launch request
userProfileUserProfileExtracted 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