Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refresh access token spring webflux #26616

Closed
germanicus9 opened this issue Jul 3, 2024 · 9 comments
Closed

refresh access token spring webflux #26616

germanicus9 opened this issue Jul 3, 2024 · 9 comments

Comments

@germanicus9
Copy link

germanicus9 commented Jul 3, 2024

Hello,

I have a jHipster Spring Boot Webflux app. and the next filter:

public class OAuth2ReactiveRefreshTokensWebFilter implements WebFilter {

    private final ReactiveOAuth2AuthorizedClientManager clientManager;

    public OAuth2ReactiveRefreshTokensWebFilter(ReactiveOAuth2AuthorizedClientManager clientManager) {
        this.clientManager = clientManager;
    }

    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
       return ReactiveSecurityContextHolder.getContext()
           .map(SecurityContext::getAuthentication)
           .filter(authentication -> authentication instanceof OAuth2AuthenticationToken)
           .cast(OAuth2AuthenticationToken.class)
           .flatMap(authentication -> {
               String clientRegistrationId = authentication.getAuthorizedClientRegistrationId();
               return clientManager.authorize(OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId)
                   .principal(authentication)
                   .attributes(attrs -> attrs.put(ServerWebExchange.class.getName(), exchange)).build())
                   .flatMap(authorizedClient -> {
                       OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
                       if (isTokenExpired(accessToken)) {
                           return refreshAccessToken(authorizedClient, authentication, exchange, chain);
                       } else {
                           return chain.filter(exchange);
                       }
                   });
           }).switchIfEmpty(chain.filter(exchange));
    }

The behaviour is the following:
this branch else {return chain.filter(exchange);} is called every time when I access the app and the access token is renewed automatically when the accessToken is expired. How is this done, by whom?
this branch return refreshAccessToken(authorizedClient, authentication, exchange, chain); is never called. Why?
In concluzion apparently OAuth2ReactiveRefreshTokensWebFilter do nothing!?? What is the goal of this filter?

On the other hand if I stay 10 min in idle mode (no click) I receive
org.springframework.security.oauth2.client.ClientAuthorizationRequiredException: [client_authorization_required] Authorization required for Client Registration Id: oidc (probably for /api/account). Who throws this error and how should be controlled?
Is there any example?

Waiting for your comments. Thank you!!

@germanicus9
Copy link
Author

germanicus9 commented Jul 3, 2024

According to https://www.baeldung.com/spring-webclient-oauth2
Spring Security 5 refresh tokens automatically

On the other hand there is an AuthorizationCodeReactiveOAuth2AuthorizedClientProvider which throws ClientAuthorizationRequiresException.
However I do not know when this AuthorizationCodeReactiveOAuth2AuthorizedClientProvider is triggered (10 min idle?!) .

@mshima
Copy link
Member

mshima commented Jul 3, 2024

By default token will be refreshed 1 min before expire:

.refreshToken(builder -> builder.clockSkew(Duration.ofMinutes(1)))

Once expired a new login is required.

@mraible
Copy link
Contributor

mraible commented Jul 3, 2024

If you're passing in an offline_access scope, you should get a refresh token and it should automatically renew when the access token expires. This blog post shows how I was able to make them work with just Spring Boot and Spring Cloud. https://auth0.com/blog/java-spring-boot-microservices/#Spring-Boot-Microservices-and-Refresh-Tokens

@germanicus9
Copy link
Author

germanicus9 commented Jul 3, 2024

I cannot use offline_access scope because I do not use Okta. However Spring Security 5 do refresh access_token automatically.
The problem is that I don't know how to control idle period for OAuth2AuthorizationContext (throws ClientAuthorizationRequiredException after 10 min of inactivity) and also I do not understand what suppose to do with OAuth2ReactiveRefreshTokenWebFilter generated together with jhipster 8.4.9 in this project repo?

@germanicus9 germanicus9 changed the title refresh access token spring webfklux refresh access token spring webflux Jul 3, 2024
@mraible
Copy link
Contributor

mraible commented Jul 3, 2024

The offline_access scope is not specific to Okta. It should work with any OAuth 2.0 server.

@germanicus9
Copy link
Author

germanicus9 commented Jul 4, 2024

Yes, but not work with my OIDC server unfortunately.
In AuthorizationCodeReactiveOAuth2AuthorizedClientProvider is stated: Throws ClientAuthorizationRequiredException - in order to trigger authorization in which the OAuth2AuthrorizationRequestRedirectWebFilter will catch and initiate the authorization.
Is there an example implementation somewhere (in this respect)?

I think is the same problem like: github.com//issues/9707
Is there any solution here? What is the conclusion? At least can I prolong/extend the idle time before authorization_token expires?

@mshima
Copy link
Member

mshima commented Jul 4, 2024

@germanicus9 the answer is here #26616 (comment).
Increasing clock skew will force the token to be renewed in the timeframe before expiring.

@germanicus9
Copy link
Author

germanicus9 commented Jul 5, 2024

Basically I redone the OAuth2ReactiveRefreshTokensWebFilter and now if I walk through the the application, the filter is called and access_token and refresh_token are changed accordingly. I use
private boolean isTokenExpired(Oauth2AccessToken accessToken) { return accessToken.getExpired()).minus(1, ChronUnit.Minutes).isBefore(Instant.now().
My problem is that in idle mode after refresh_token expiration I get an error (which is normal).
Is not clear what this @bean does:

        authorizedClientManager.setAuthorizedClientProvider(
            OAuth2AuthorizedClientProviderBuilder
                .builder()
                .authorizationCode()
                .refreshToken(builder -> builder.clockSkew(Duration.ofMinutes(1)))
                .clientCredentials()
                .build()
        );

refreshes token automatically regardless inactivity period or what? @mshima can provide more details, pls?

@mshima
Copy link
Member

mshima commented Jul 5, 2024

clockSkew is used to avoid token timestamp validation and the server timestamp differences conflict.
The token timestamp will be validated according the server timestamp plus the clockSkew.
If the token is expired according to the validation it will be renewed.
The default clockSkew provided by Spring Boot is 60s like the configuration.

So if clockSkew is half of token validation, the token will be refreshed after half of its lifetime.
I don't know if there is a downside in this approach, but IFAIK it's the only way to make Spring Boot refresh it automatically.
In any case this is quite technical question so you should post in stackoverflow to get a broader Spring Boot audience.

@mshima mshima closed this as completed Jul 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants