spring-security icon indicating copy to clipboard operation
spring-security copied to clipboard

Support for JWT Header TYP as "at+jwt"

Open tekgainers opened this issue 4 years ago • 13 comments

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 avatar Jun 11 '21 11:06 tekgainers

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

Thanks in advance.

tekgainers avatar Jun 23 '21 10:06 tekgainers

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.

jzheaux avatar Jul 06 '21 23:07 jzheaux

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

shartte avatar Apr 05 '23 11:04 shartte

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.

jzheaux avatar May 19 '23 21:05 jzheaux

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

ymajoros avatar May 22 '23 06:05 ymajoros

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

jzheaux avatar May 22 '23 18:05 jzheaux

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):

  • if we strictly follow RFC 9068, we should only accept application/at+jwt or at+jwt (the latter being recommended)
  • besides the typ check already checked in this pull request, we should check:
    • iss, which we already do
    • signature, which we already do
    • exp, which we already do
    • aud, which we don't, but I suggest to keep this as a stricter option for later, as we should also check it for plain jwt anyway, according to RFC 7519

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

ymajoros avatar Jul 04 '23 12:07 ymajoros

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

shartte avatar Jul 04 '23 12:07 shartte

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

ymajoros avatar Jul 04 '23 13:07 ymajoros

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

ymajoros avatar Jul 12 '23 12:07 ymajoros

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

burl21 avatar Sep 05 '23 09:09 burl21

Is there any recommended workaround?

daniel-susumu avatar Apr 08 '24 16:04 daniel-susumu

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.

bilalkais avatar Sep 11 '24 12:09 bilalkais

Since there is a PR, I'll close this ticket in favor of that and we can continue the conversation over there: #13186

jzheaux avatar Feb 21 '25 21:02 jzheaux