Expected Behavior
Introduce opt-in API to initialize Bearer Token before first request, and to refresh this token periodically in background.
Current Behavior
Currently, WebClient configured with Bearer Token authentication like Client Credentials will not retrieve token unless request is made (which I think is proper default behavior), also, when token expires, new token will be fetched only when next request is done. This is ok for default case, but it introduces delays in request processing at random moments.
Sometimes you are willing to pay some cost of prefetching token before use, and periodically fetching it in background to make sure requests will not be delayed. This is not possible with current implementation.
Context We have rest microservice (let's call it A) that has other rest microservice as dependency (B), which is read with WebClient configured with Client Credentials for authentication, with external authorization server. Pretty common scenario. Only after first request arrives to A and request is triggered to B WebClient fetches token, so first request to A is delayed by time required to retrieve token, in our case it was 2s, so much to long, causing request to fail because of timeout. Same situation happens when token expires - either token is valid and used for request, or is considered expired and request is delayed until new token is fetched.
I was able to implement own ClientCredentialsReactiveOAuth2AuthorizedClientProvider that will refresh token in background when it is beyond some lifetime threshold, still using old token until it's ready. But maybe this could be also useful for everyone else?
More complex thing is with prefetching (before first request) and refreshing even when there are no requests.
For prefetching, I was able to do it by creating proxy AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager that had some preconfigured OAuth2AuthorizeRequest injected, and would trigger authorization request with it. It works, but I don't like it as it breaks responsibilities. But I was not able to find any API that could be used.
For periodic refresh (that would trigger without any request to this webclient) there is an issue that there needs to be some handle to stop refreshing, preferably without user intervention when WebClient is not used anymore.
Note There is symmetric issue on server side with JWK fetch - it is only fetched when it is needed, delaying first request. Should I include it here or raise another issue?
Implementation details for solution I made
For ClientCredentialsReactiveOAuth2AuthorizedClientProvider I check if token is valid. If not, current implementation is used (blocking request until token is fetched). If valid, I calculate token lifetime, and when token is past percentage of lifetime I issue request in background if not issued already, and return Mono.empty() to trigger usage of old token. To simplify implementation I use Mono.cache() to create cached hot source of tokens, then use Mono.or() to select new token when it is ready in cache, as in tokenMonoCached.or(Mono.empty()). This will immediately return result, either new token or empty, but assumes that Mono.or() will evaluate in order of arguments to or. While it works this way I was not able to find confirmation in reactor docs. But if this is not like this it could be reworked to some Atomic<>.
For AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager I wrote some proxy that accepts OAuth2AuthorizeRequest as costructor parameter, and exposes start() and stop() APIs to trigger initial token fetch and start periodic requests. Periodic requests are triggered once i a while (configurable) with Flux.interval().
As I mentioned, I'm not a fan of this solution because it requires AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager to have some preconfigured OAuth2AuthorizeRequest and is not usable for generic case.
Periodic trigger of .authorize() should not be a problem, as when token is valid it will just check validity and return empty mono.
Comment From: jgrandja
@piotrplazienski
prefetching token before use, and periodically fetching it in background to make sure requests will not be delayed
This is application-specific behaviour so it would need to be implemented in the application itself. Our focus is to provide protocol (spec) implementations only within the framework.
Please take a look at the comments in gh-7676 as a solution is provided there along with a Stack Overflow answer.
I'll close this as a duplicate.
Comment From: piotrplazienski
@jgrandja I think it's fair design decision if you want keep such behaviour outside of framework, as sufficient API is exposed to inject your own behavior. This is exactly what we did. Thank you for clarification and pointer, this confirms we had right idea :).
I don't want to spam new issues without asking, so let me ask here:
What about server side (fetching JWKI)? There's Nimbus[Reactive]JwtDecoder, but it's API is locked tight - I cannot find any way to inject provider of JWK set. There is option to inject webclient, and theoretically I could implement mock implementation that would return cached and refreshed JWKS, but WebClient api is broad, complex and it's almost guaranteed that something would not go way we intend.
I would rather not rewrite whole JwtDecoder from scratch, as this is security and I would rather use tried and tested things.
There is issue #8882 that calls for webclient customization, but would not help with complete replacement of JWKS provider.
Is there something I am missing? Should I raise another issue?
Comment From: jgrandja
@piotrplazienski
I cannot find any way to inject provider of JWK set.
I believe this can be achieved via NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder or directly calling NimbusReactiveJwtDecoder(Converter<JWT, Mono<JWTClaimsSet>> jwtProcessor).
Please log a new issue and /cc @jzheaux as he would be able to help out.
Comment From: piotrplazienski
Unfortunately NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder has no public API to set ReactiveJWKSource, and creating NimbusReactiveJwtDecoder directly requires to construct whole Converter<JWT, Mono<JWTClaimsSet>> jwtProcessor, with all verifiers, selectors and algorithms, which I would rather not do.
Thank you very much for your help, I really appreciate it. I will create issue.
Comment From: piotrplazienski
Ok disregard my comment. There is NimbusReactiveJwtDecoder#withJwkSource which allows you to specify provider of JWK