1
0
mirror of synced 2026-05-22 13:23:17 +00:00

Fix ID Token auth_time validation

Closes gh-18839
This commit is contained in:
Joe Grandja
2026-03-25 11:33:55 -04:00
parent b6e24db68c
commit 6e683f2286
2 changed files with 26 additions and 4 deletions
@@ -300,7 +300,11 @@ public final class OidcAuthorizedClientRefreshedEventListener
return;
}
if (!idToken.getAuthenticatedAt().equals(existingOidcUser.getIdToken().getAuthenticatedAt())) {
// The auth_time claim MUST represent the time of the original authentication OR
// the most recent time when the end-user reauthenticated when "prompt=login" is
// passed in the authentication request
if (!idToken.getAuthenticatedAt().equals(existingOidcUser.getIdToken().getAuthenticatedAt())
&& !idToken.getAuthenticatedAt().isAfter(existingOidcUser.getIdToken().getAuthenticatedAt())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid authenticated at time",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
@@ -419,8 +419,8 @@ public class OidcAuthorizedClientRefreshedEventListenerTests {
}
@Test
public void onApplicationEventWhenIdTokenAuthenticatedAtDoesNotMatchThenThrowsOAuth2AuthenticationException() {
Instant authTime = this.oidcUser.getAuthenticatedAt().plus(5, ChronoUnit.SECONDS);
public void onApplicationEventWhenIdTokenAuthenticatedAtBeforeCurrentAuthenticatedAtThenThrowsOAuth2AuthenticationException() {
Instant authTime = this.oidcUser.getAuthenticatedAt().minus(5, ChronoUnit.MINUTES);
Jwt jwt = createJwt().claim(IdTokenClaimNames.AUTH_TIME, authTime).build();
SecurityContextImpl securityContext = new SecurityContextImpl(this.authentication);
given(this.securityContextHolderStrategy.getContext()).willReturn(securityContext);
@@ -440,8 +440,26 @@ public class OidcAuthorizedClientRefreshedEventListenerTests {
verifyNoInteractions(this.userService, this.applicationEventPublisher);
}
// gh-18839
@Test
public void onApplicationEventWhenIdTokenAuthenticatedAtMatchesThenOidcUserRefreshedEventPublished() {
public void onApplicationEventWhenIdTokenAuthenticatedAtAfterCurrentAuthenticatedAtThenOidcUserRefreshedEventPublished() {
Instant authTime = this.oidcUser.getAuthenticatedAt().plus(5, ChronoUnit.MINUTES);
Jwt jwt = createJwt().claim(IdTokenClaimNames.AUTH_TIME, authTime).build();
SecurityContextImpl securityContext = new SecurityContextImpl(this.authentication);
given(this.securityContextHolderStrategy.getContext()).willReturn(securityContext);
given(this.jwtDecoder.decode(anyString())).willReturn(jwt);
given(this.userService.loadUser(any(OidcUserRequest.class))).willReturn(this.oidcUser);
OAuth2AuthorizedClientRefreshedEvent authorizedClientRefreshedEvent = new OAuth2AuthorizedClientRefreshedEvent(
this.accessTokenResponse, this.authorizedClient);
this.eventListener.onApplicationEvent(authorizedClientRefreshedEvent);
verify(this.applicationEventPublisher).publishEvent(any(OidcUserRefreshedEvent.class));
verifyNoMoreInteractions(this.applicationEventPublisher);
}
@Test
public void onApplicationEventWhenIdTokenAuthenticatedAtMatchesCurrentAuthenticatedAtThenOidcUserRefreshedEventPublished() {
Instant authTime = this.authentication.getPrincipal().getAttribute(IdTokenClaimNames.AUTH_TIME);
Jwt jwt = createJwt().claim(IdTokenClaimNames.AUTH_TIME, authTime).build();
SecurityContextImpl securityContext = new SecurityContextImpl(this.authentication);