diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java index 1e983522e9..841c8f268e 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java @@ -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()); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListenerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListenerTests.java index f3521d43d9..e0aa5cbc72 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListenerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListenerTests.java @@ -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);