OIDC & User Profile
id_token validationโ
IdTokenValidator.validate(String idToken, SmartLaunchSession session) performs full RS256 validation:
SignedJWT jwt = SignedJWT.parse(idToken);
JWTClaimsSet claims = jwt.getJWTClaimsSet();
// 1. Algorithm check
if (!"RS256".equals(jwt.getHeader().getAlgorithm().getName())) {
throw new OidcValidationException("Expected RS256, got: " + algorithm);
}
// 2. Issuer
if (!discoveredIss.equals(claims.getIssuer())) {
throw new OidcValidationException("Issuer mismatch");
}
// 3. Audience
if (!claims.getAudience().contains(smartProperties.getClientId())) {
throw new OidcValidationException("Audience mismatch");
}
// 4. Expiry
if (claims.getExpirationTime().before(new Date())) {
throw new OidcValidationException("id_token expired");
}
// 5. Nonce
if (!session.getNonce().equals(claims.getStringClaim("nonce"))) {
throw new OidcValidationException("Nonce mismatch");
}
// 6. Signature โ verify against JWKS
RSAKey rsaKey = fetchPublicKey(claims.getKeyID(), discoveryConfig.jwksUri());
RSASSAVerifier verifier = new RSASSAVerifier(rsaKey);
if (!jwt.verify(verifier)) {
throw new OidcValidationException("Signature verification failed");
}
JWKS cachingโ
IdTokenValidator fetches the JWKS from jwks_uri on first validation and caches the key set for 1 hour. Key rotation (new kid) triggers an automatic re-fetch.
UserProfile extractionโ
After successful id_token validation, UserProfileService builds a UserProfile:
public record UserProfile(
String subject, // sub claim
String fhirUser, // fhirUser claim โ e.g. "Practitioner/dr-smith"
String name, // name claim
String practitionerId // extracted from fhirUser
) {}
The fhirUser claim is used to load the Practitioner resource from FHIR when the dashboard is rendered, populating the clinician profile card.
Using UserProfile in controllersโ
@GetMapping("/dashboard")
public String dashboard(HttpSession session, Model model) {
SmartLaunchContext ctx = getContext(session);
model.addAttribute("user", ctx.getUserProfile());
model.addAttribute("patient", loadPatient(ctx));
return "dashboard";
}
In the Thymeleaf template:
<div class="clinician-name" th:text="${user.name}">Dr. Smith</div>
<div class="fhir-user" th:text="${user.fhirUser}">Practitioner/dr-001</div>
Next: Token Refresh โ