spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.72k stars 5.87k forks source link

Support for JWT Header TYP as "at+jwt" #9900

Open tekgainers opened 3 years ago

tekgainers commented 3 years ago

Expected Behavior

Currently, if the JWT is having typ as "at+jwt", the token is rejected with message "Failed to authenticate since the JWT was invalid". Spring Security Oauth2 Resource Server with JWT as bearer token should accept typ as "at+jwt" as well.

Current Behavior

Currently, if the JWT is having typ as "at+jwt", the token is rejected with message "Failed to authenticate since the JWT was invalid".

My Authorization server is issuing JWT access token with typ as "at+jwt" as per the following draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-access-token-jwt-11

How has this issue affected you? JWT token is rejected although this is correct as per Authorization server What are you trying to accomplish? Validate JWT Bearer token using Spring Security OAuth2 Resource Server capabilities. What other alternatives have you considered? At this point, in the documentation I didn;t find a way where I can customize the JWT Validator. Although, documentation is around our own Audience or other validations once the JWT is accepted by Spring Security. Are you aware of any workarounds? Not at this point of time.

tekgainers commented 3 years ago

@jzheaux can you please have a look if this is bug or this needs enhancement in library?

Thanks in advance.

jzheaux commented 3 years ago

The reason for this behavior is Nimbus defaults to checking that the typ is JWT. I am not certain whether Nimbus yet supports the at+jwt draft - Spring Security would likely rely on Nimbus for this kind of support.

To get your resource server to accept "typ" : "at+jwt", you can configure Nimbus like so:

@Bean 
JwtDecoder jwtDecoder() {
    DefaultJOSEObjectTypeVerifier<SecurityContext> verifier =
        new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("at+jwt"));
    NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(this.uri)
        .jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(verifier))
        .build()
        // ... any other decoder settings
    return decoder;
}

For passivity reasons, I imagine that Spring Security will continue to use the Nimbus defaults. Once the draft gets finalized, there may be an opportunity to make this configuration simpler.

shartte commented 1 year ago

@jzheaux The draft has now been published as RFC 9068 (and I am running into this problem, thanks for the configuration hint, however :-))

jzheaux commented 1 year ago

In light of a separate request from @ymajoros, I think it would be good to revisit this decision, now that the spec is finalized.

Allowing the at+jwt type is not sufficient to support RFC 9068. If an application allows at+jwt, it should also validate those tokens as per the at+jwt spec as well.

I think this would fit nicely into JwtValidators#createDefault and JwtValidators#createDefaultForIssuer:

@Bean 
JwtDecoder jwtDecoder() {
    NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation("http://issuer").build();
    jwtDecoder.setJwtValidator(JwtValidators.createDefaultForIssuer("http://issuer"));
    return jwtDecoder;
}

where these factory methods change to check the typ field first before deciding on what validation to perform. If it is at+jwt, then it performs the at+jwt validations; otherwise, it does what it has always done.

In this case, NimbusJwtDecoder sets JWSTypeVerifier such that at+jwt, jwt, and null are allowed by Nimbus.

This changes how at+jwt is processed by Spring Security; however, I would consider it a bug if an application is accepting at+jwt tokens today without also validating them.

ymajoros commented 1 year ago

Thanks @jzheaux . What about this pull request, which solves the problem IMO? https://github.com/spring-projects/spring-security/pull/13186

jzheaux commented 1 year ago

@ymajoros, thank you very much for the PR. I don't think that it should be in JwtDecoders, but NimbusJwtDecoder. Also, it is missing the needed validation. Please see my comment about what needs to be done to add this support.

ymajoros commented 1 year ago

After spending some more time on it, I'm not convinced we can do more validation for now, according to RFC 9068 (at+jwt) and RFC 7519 (jwt):

So, I think the current implementation is enough. I'd just add the optional application/ prefix.

shartte commented 1 year ago

@ymajoros If it isn't already, I think it should be possible to configure the expected audience values. At least it wouldn't allow tokens leaked by one service (i.e. in a microservice architecture) to be successfully used against another service.

ymajoros commented 1 year ago

@shartte true, but that seems out of scope for this issue and a different feature that should also be implemented, isn't it?

ymajoros commented 1 year ago

@jzheaux can you look at it again in light of my previous comments?

burl21 commented 1 year ago

Is there any update on "at+jwt" support ?

daniel-susumu commented 5 months ago

Is there any recommended workaround?

bilalkais commented 6 days ago

Any updates on this one @jzheaux?

We've used the following workaround in our application:

  @Bean
  JwkSetUriJwtDecoderBuilderCustomizer jwkSetUriJwtDecoderBuilderCustomizer() {
    final var verifier = new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("at+jwt"));
    return builder -> builder.jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(verifier));
  }

This approach works well when using either resourceserver.jwt.issuer-uri or resourceserver.jwt.jwk-set-uri, both of which rely on the jwkSetUriJwtDecoderBuilder. However, when using resourceserver.jwt.public-key-location, we don't have a way to customize the JWT processor without creating a completely new decoder. This is problematic as it leads to losing access to features like the audience validator, which would otherwise be provided.