Expected Behavior

When we create a NimbusReactiveJwtDecoder with NimbusReactiveJwtDecoder.withJwkSetUri and setting the webclient, then the JWK Set should be loaded on startup and not upon the first request.

    var decorder = NimbusReactiveJwtDecoder
        .withJwkSetUri(props.getIssuer() + JWKS_PATH)
        .webClient(webClient)
        .build();

Current Behavior

The NimbusReactiveJwtDecoder loads the JwkSet on the first request to the app rather than upon application start up.

Context The NimbusReactiveJwtDecoder has a processor that new's up a ReactiveRemoteJWKSource and it is this that holds the JwkSet cache. This cache only gets populated on the first request to the app. Currently there is no way to get a hold of this without writing out own NimbusReactiveJwtDecoder.
We have a work around by making a decode call using a fake token as part of the configuration class to warm up the cache, but this leaves a nasty error message in our application start up logs so we want the option to do this correctly and not have to use a work around. Our current workaround :- decorder.decode(FAKE_TOKEN).doFinally(signal -> log.info("jwks.json Cache Warmed")).subscribe();

Our jwks.json can take up to 6 seconds to download, so the first unlucky person to hit a restarted service will need to wait this time.

Comment From: skjolber

Should this not be tied in to the health indicators? i.e. the app is not ready unless JWKs have been loaded.

Comment From: marcusdacoregio

Hi @biggogibbo, thanks for reaching out.

Based on the reference documentation and on https://github.com/spring-projects/spring-security/issues/9991, this is the expected behavior since it relies on Nimbus, which operates lazily.

You can create an eager ReactiveJwtDecoder by providing a bean with the issuer-uri, like so:

@Bean
ReactiveJwtDecoder eagerJwtDecoder() {
    return ReactiveJwtDecoders.fromIssuerLocation(issuerUri);
}

Can you confirm that this works for you?

Comment From: cselagea

Should this not be tied in to the health indicators? i.e. the app is not ready unless JWKs have been loaded.

If your resource server has a single tenant, this may be desirable. But if you're serving multiple tenants, then you may not want your application startup/readiness to be blocked by one tenant's issuer being unavailable.

Comment From: biggogibbo

Hi @biggogibbo, thanks for reaching out.

Based on the reference documentation and on #9991, this is the expected behavior since it relies on Nimbus, which operates lazily.

You can create an eager ReactiveJwtDecoder by providing a bean with the issuer-uri, like so:

java @Bean ReactiveJwtDecoder eagerJwtDecoder() { return ReactiveJwtDecoders.fromIssuerLocation(issuerUri); }

Can you confirm that this works for you?

Hi

So we are using Webflux and our solution needs to be reactive. But trying the solution above I can see that this loads the JwkSet twice and it also doesn't allow us to provide a sensible timeout for retrieving the JwkSet.

ReactiveJwtDecoders.fromIssuerLocation(issuerUri)

calls

private static ReactiveJwtDecoder withProviderConfiguration(Map<String, Object> configuration, String issuer) {

....  
RemoteJWKSet<SecurityContext> jwkSource = **new RemoteJWKSet(url(jwkSetUri))**;
    Set<SignatureAlgorithm> signatureAlgorithms = JwtDecoderProviderConfigurationUtils.getSignatureAlgorithms(jwkSource);
    NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).jwsAlgorithms((algs) -> {
      algs.addAll(signatureAlgorithms);
    }).build();

And the jwkSource is downloaded as part of the jwsAlgorithms call.

But when the first request comes in I can see the following is run in ReactiveRemoteJWKSource

public Mono<List<JWK>> get(JWKSelector jwkSelector) {

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

The cachedJWKSet is empty so this will invoke the getJWKSet() which will go and download the JwkSet again. This second time is invoked when the first request is sent.

We also need to be able to configure a sensible timeout on the webclient being used, hence we used the following code in order to set the timeout var decorder = NimbusReactiveJwtDecoder .withJwkSetUri(props.getIssuer() + JWKS_PATH) .webClient(webClient) .build();

So Im afraid the solution you suggested wont work for us

Comment From: skjolber

Should this not be tied in to the health indicators? i.e. the app is not ready unless JWKs have been loaded.

If your resource server has a single tenant, this may be desirable. But if you're serving multiple tenants, then you may not want your application startup/readiness to be blocked by one tenant's issuer being unavailable.

It should be configurable which tenants are required for readiness and which are optional.

Comment From: jzheaux

As a side note, https://github.com/spring-projects/spring-security/issues/10312 ensures that the JWKSource is only queried once on startup when using ReactiveJwtDecoders#withIssuerLocation.

I agree that it would be nice to be able to provide a pre-fetching JWK source. I wonder if Nimbus would be open to a contribution?

I'm going to close this as a duplicate of https://github.com/spring-projects/spring-security/issues/9646 since I imagine the work needed will be the same.