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

Introduce Bearer Token prefetch and background refresh for WebClient Oauth2 #9560

Closed
piotrplazienski opened this issue Apr 6, 2021 · 5 comments
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: duplicate A duplicate of another issue

Comments

@piotrplazienski
Copy link

piotrplazienski commented Apr 6, 2021

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.

@piotrplazienski piotrplazienski added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Apr 6, 2021
@jgrandja
Copy link
Contributor

@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.

@jgrandja jgrandja added in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Apr 13, 2021
@piotrplazienski
Copy link
Author

@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?

@jgrandja
Copy link
Contributor

@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.

@piotrplazienski
Copy link
Author

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.

@piotrplazienski
Copy link
Author

Ok disregard my comment. There is NimbusReactiveJwtDecoder#withJwkSource which allows you to specify provider of JWK

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) status: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests

2 participants