Skip to content

Conversation

@evgeniycheban
Copy link
Contributor

Closes gh-17188

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jun 14, 2025
@jgrandja jgrandja self-assigned this Jun 16, 2025
@jgrandja jgrandja added type: enhancement A general enhancement in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) and removed status: waiting-for-triage An issue we've not yet triaged labels Jun 16, 2025
@jgrandja jgrandja added this to the 7.0.x milestone Jun 16, 2025
@evgeniycheban evgeniycheban marked this pull request as draft June 17, 2025 23:40
@jgrandja jgrandja modified the milestones: 7.0.x, 7.1.0-M1 Nov 12, 2025
@jgrandja
Copy link
Contributor

Thanks for your patience @evgeniycheban and apologies that I could not get this into 7.0. We had quite a workload for this round of majors.

I have scheduled this for 7.1.0-M1 and will review this soon. In the meantime, when you have a moment can you please rebase. Thank you.

@evgeniycheban evgeniycheban force-pushed the gh-17188 branch 2 times, most recently from be68a71 to ac99b3b Compare November 18, 2025 22:00
@evgeniycheban
Copy link
Contributor Author

Hi @jgrandja, I have rebased my branch and also updated license headers to meet new pattern, let me know your feedback once you review it, thanks.

@jgrandja
Copy link
Contributor

@evgeniycheban I took a look at the PR and noticed that the implementation is different compared to the Servlet implementation.

RefreshTokenReactiveOAuth2AuthorizationSuccessHandler combines the logic in OidcAuthorizedClientRefreshedEventListener and OidcUserRefreshedEventListener and does not make use of Spring Framework's ApplicationEventPublisher and ApplicationListener.

The preference is to keep the Servlet and Reactive implementations as close as possible. Is there a reason you did not make use of ApplicationEventPublisher and ApplicationListener ?

Just a heads up that I'm on PTO starting tomorrow and returning Jan 5 so I'll reply when I return and we can work through this.

@evgeniycheban
Copy link
Contributor Author

Hi @jgrandja, thank you for reviewing it. The reason I did not use the ApplicationEventPublisher is because in the current implementation there is no support for reactive application events, I can recall that there was a draft PR raised by @marcusdacoregio but it didn't get merged.

@jgrandja
Copy link
Contributor

jgrandja commented Jan 9, 2026

@evgeniycheban Got it. I'll do a detailed review next week and we can work towards merging this.

@jgrandja jgrandja modified the milestones: 7.1.0-M1, 7.1.x, 7.1.0-M2 Jan 19, 2026
Copy link
Contributor

@jgrandja jgrandja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your patience @evgeniycheban.

Overall, this looks good and I believe we are fairly close to merging.

Please see my review comments for further updates.


private ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = new WebClientReactiveRefreshTokenTokenResponseClient();

private ReactiveOAuth2AuthorizationSuccessHandler refreshTokenSuccessHandler = new RefreshTokenReactiveOAuth2AuthorizationSuccessHandler();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReactiveOAuth2AuthorizationSuccessHandler is not designed to be used with a ReactiveOAuth2AuthorizedClientProvider and instead is meant to be used with a ReactiveOAuth2AuthorizedClientManager.

Can you re-work this and set RefreshTokenReactiveOAuth2AuthorizationSuccessHandler in DefaultReactiveOAuth2AuthorizedClientManager ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* Connect Core 1.0 - Section 12.2 Successful Refresh Response</a>
*
* @author Evgeniy Cheban
* @since 7.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update to 7.1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@Override
public Mono<Void> onAuthorizationSuccess(OAuth2AuthorizedClient authorizedClient, Authentication principal,
Map<String, Object> attributes) {
if (!(principal instanceof OAuth2AuthenticationToken authenticationToken)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OidcAuthorizedClientRefreshedEventListener performs a couple of validation checks that are missing here. Specifically, checks OAuth2AccessTokenResponse to ensure openid scope is associated with the access token and an id_token is returned. These pre-checks should be in place as we only want to proceed if this is an OIDC refresh token flow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

.transform((responseMono) -> this.clientResponseHandler.handleResponse(request, responseMono));
// Re-request an Authentication from serverSecurityContextRepository since it
// might have been changed during provider invocation.
return effectiveAuthentication(request).flatMap((authentication) -> next.exchange(request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding this logic, can we not update the SecurityContext in the reactor context in RefreshTokenReactiveOAuth2AuthorizationSuccessHandler right after this.serverSecurityContextRepository.save(exchange, securityContext) to avoid this serverSecurityContextRepository.load()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tricky part here is that we can't propagate the refreshed SecurityContext to the upstream in a reactor context, it is propagated always one-way from upstream to downstream, so that's why I used saving the refreshed SecurityContext in ServerSecurityContextRepository to be able later to continue the reactive chain with the refreshed SecurityContext and propagate it using contextWrite.

I also considered using a custom CoreSubscriber similar to how it's done in SecurityReactorContextConfiguration, but I'm not sure if it would be the right approach here.
What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is propagated always one-way from upstream to downstream

I'm not sure I understand this?

The tricky part here is that we can't propagate the refreshed SecurityContext to the upstream in a reactor context

Please see the following issue to see if it helps?

#16665

Copy link
Contributor Author

@evgeniycheban evgeniycheban Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will have a look at context-propagation library, thank you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the point you provided here @jgrandja, however, in order to propagate the refreshed SecurityContext to the next ExchangeFilterFunction in ServerOAuth2AuthorizedClientExchangeFilterFunction:

return next.exchange(request)
	.transform((responseMono) -> this.clientResponseHandler.handleResponse(request, responseMono));

updating SecurityContext in the reactor context in RefreshTokenReactiveOAuth2AuthorizationSuccessHandler has no effect, so we need to write the new context to the reactive chain before calling next.exchange(request):

return effectiveAuthentication(request).flatMap((authentication) -> next.exchange(request)
	.transform((responseMono) -> this.clientResponseHandler.handleResponse(request, responseMono))
	.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication)));

At this point we load the SecurityContext to get the most recent version to be passed to the downstream.

Another option could be moving the logic from RefreshTokenReactiveOAuth2AuthorizationSuccessHandler to RefreshTokenReactiveOAuth2AuthorizedClientProvider and enriching an OAuth2AuthorizedClient with the new Authentication object when the token is refreshed and then propagate it to the downstream to the next ExchangeFilterFunction in ServerOAuth2AuthorizedClientExchangeFilterFunction.

@jgrandja, I'm also available for a short call if you want to discuss this in more details, just let me know, thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jgrandja I can as well commit the change that would demonstrate the failing test if we do .contextWrite in RefreshTokenReactiveOAuth2AuthorizationSuccessHandler instead of ServerOAuth2AuthorizedClientExchangeFilterFunction, please let me know.

* @param serverSecurityContextRepository the {@link ServerSecurityContextRepository}
* to use
*/
public void setServerSecurityContextRepository(ServerSecurityContextRepository serverSecurityContextRepository) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the public methods above the private methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@evgeniycheban evgeniycheban force-pushed the gh-17188 branch 3 times, most recently from 8874f50 to 1ea9456 Compare February 2, 2026 20:07
@evgeniycheban evgeniycheban marked this pull request as ready for review February 2, 2026 22:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ensure ID Token is updated after refresh token (Reactive)

3 participants