Skip to main content

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 โ†’