Summary

In NimbusReactiveJwtDecoder there is ReactiveRemoteJWKSource instance created when used with JWK Set URI. I didn't find any way to refresh the cached JWK set in ReactiveRemoteJWKSource. Once it's populated on the very first call to ReactiveRemoteJWKSource.get(JWKSelector jwkSelector) it is not possible to refresh it. Correct me if I'm wrong.

It seems that it was planned to be implemented as there is this comment in the get(JWKSelector jwkSelector, JWKSet jwkSet) method (line 71) but without implementation:

// Refresh the JWK set if the sought key ID is not in the cached JWK set

This issue does not apply to the non-reactive NimbusJwtDecoder which internally uses the original RemoteJWKSet directly from the Nimbus lib. Their implementation at least has the possibility to set cache invalidation timeout (5 mins by default, see com.nimbusds.jose.jwk.source.DefaultJWKSetCache).

Version

5.2.0.RC1

Comment From: jzheaux

That actually appears to be a stray comment. Caching is implemented to be invalidated whenever there's an unrecognized kid:

return this.cachedJWKSet.get()
    .switchIfEmpty(Mono.defer(() -> getJWKSet()))
    .flatMap(jwkSet -> get(jwkSelector, jwkSet))
    .switchIfEmpty(Mono.defer(() -> getJWKSet().map(jwkSet -> jwkSelector.select(jwkSet))));

That said, I agree that it would be nice to be able to have a timed expiration as well.

Comment From: janterkanic

Agreed, if there is a new kid everything works well. But we are not able to react if some existing kid get's deleted by authorization server (and all tokens signed with it should be considered invalid), e.g. during a standard certificate exchange process. The process mostly ensures that for some specified period the new kid and also the old one are valid. But after that only the new one is retained.

Comment From: jzheaux

Agreed, @janterkanic.

I'll leave this ticket open while investigating the ideal way to provide a timed expiration.

Comment From: rajc28

I am also interested in setting the cache expiry time for JWKSet

Comment From: Aniket-Singla

Hi, other from cache invalidation, can we have to ability to cache jwk's according to different tenants. I believe it will be a very common use case. Currently ReactiveRemoteJWKSource can cache assuming that we only have single type of jwk's. Each time a new kid comes into picture the existing values in cachedJWKSet ( in org.springframework.security.oauth2.jwt.ReactiveRemoteJWKSource) is replaced with new jwk set while the existing values in cachedJWKSet are dropped.

Thanks.

Comment From: jzheaux

Hi, @Aniket-Singla. Have you already tried AuthenticationManagerResolver? This is the intended configuration point for resource server multi-tenancy.

If that gives you trouble, please consider posting a question to StackOverflow, and we'd be happy to help over there.

Comment From: datagitlies

That actually appears to be a stray comment. Caching is implemented to be invalidated whenever there's an unrecognized kid:

java return this.cachedJWKSet.get() .switchIfEmpty(Mono.defer(() -> getJWKSet())) .flatMap(jwkSet -> get(jwkSelector, jwkSet)) .switchIfEmpty(Mono.defer(() -> getJWKSet().map(jwkSet -> jwkSelector.select(jwkSet))));

That said, I agree that it would be nice to be able to have a timed expiration as well.

@jzheaux I'm having the same concern as @janterkanic where my application does not react when a kid is removed by the authorization server. I noticed this issue was removed from the 5.3.x milestone. Curious where this stands as a priority in terms of an enhancement request. Given that the servlet implementation automatically sets up a 15 minute lifespan and 5 minute refresh interval for a Remote JWK Set... I was really hoping to see some parity on the reactive side of the house. Especially since this does have a security implication (i.e. continuing to successfully validate a JWT locally with a kid that was removed / revoked by the authorization server several hours, days, weeks ago).

Comment From: Drevsh

We are having the same concerns as @datagitlies . When a key is rotated the JWKS is not refreshed and the old key is kept forever in memory. We would need to redeploy our complete backend to purge this key from memory.

It would be awesome if you could take a look at this or provide some info how we could prevent this issue.

Comment From: TshRahul

We have also faced same issue mentioned by @Drevsh recently while using NimbusReactiveJwtDecoder, whenever the certificates get renewed. Our service started giving 401. As soon as we restart the service. It started working