Describe the bug OidcIdTokenDecoderFactory by default defines jwsAlgorithmResolver with RS256 as signature algorithm for Signed JWTs (here at https://github.com/spring-projects/spring-security/blob/1edfa07d27a17ebdd0da88f6ba23ce333cba7a37/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java#L86)
It provides a setter method to override and supply your own algorithm resolver (here at https://github.com/spring-projects/spring-security/blob/1edfa07d27a17ebdd0da88f6ba23ce333cba7a37/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java#L221).
However, the way jwtAlgorithmResolver is being used to create NimbusJwtDecoder in buildDecoder() function (here https://github.com/spring-projects/spring-security/blob/1edfa07d27a17ebdd0da88f6ba23ce333cba7a37/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java#L140) , it enforces only one algorithm to be supplied to NimbusJwtDecoder which it uses internally during the key selection process.
This implementation works fine if IDP uses only one algorithm at a time as in that case, we can customize the algorithm to be used by supplying your custom jwtAlgorithmResolver using setJwsAlgorithmResolver() setter method.
However, if IDP uses more than one algorithms to sign ID Token, there is no way we can supply all the algorithms used by IDP and so the token processing fails during the whole OIDC based authentication process when a token is signed with a different algorithm than what's used to initialize NimbusJwtDecoder through OidcIdTokenDecoderFactory.
To Reproduce Have IDP use more than one algorithm (say RS256 and ES256 both) to sign JWT and make sure it randomly alternates between these algorithms for oidc based authentication request flow.
Expected behavior As long as user has authenticated successfully, the client app should be able to process and validate IDTokens regardless of which algorithm was used out of the set of configured algorithms.
Sample
Unfortunately, I can't share any code as it's private to the organization I work for, however, I'll update this ticket if I can somehow create a sample with some public IDP (e.g. google) where I can configure multiple algorithms.
Comment From: jgrandja
@jigneshshukla I'm not seeing the issue you are having.
The Map of jwtDecoders is keyed by clientRegistration.getRegistrationId(), which means each ClientRegistration has it's own JwtDecoder instance.
A custom jwsAlgorithmResolver would receive a ClientRegistration and it should return the JwsAlgorithm that is expected to be used by the provider for the the ID Token signature. The key word to note is expected, because when a client is registered with a provider, part of it's registration metadata is id_token_signed_response_alg. Therefore, if the custom jwsAlgorithmResolver is configured correctly then there shouldn't be any issues with ID Token signature verification.
Can you provide more details or a minimal sample or test that reproduces the issue so I can troubleshoot further?
Comment From: jigneshshukla
@jigneshshukla I'm not seeing the issue you are having.
The
MapofjwtDecodersis keyed byclientRegistration.getRegistrationId(), which means eachClientRegistrationhas it's ownJwtDecoderinstance.A custom
jwsAlgorithmResolverwould receive aClientRegistrationand it should return theJwsAlgorithmthat is expected to be used by the provider for the the ID Token signature. The key word to note is expected, because when a client is registered with a provider, part of it's registration metadata is id_token_signed_response_alg. Therefore, if the customjwsAlgorithmResolveris configured correctly then there shouldn't be any issues with ID Token signature verification.Can you provide more details or a minimal sample or test that reproduces the issue so I can troubleshoot further?
@jgrandja - I agree with what you are saying and it is working as expected in the scenarios where IDP is using only one algorithm to sign ID Token. However, as per OpenID Spec (https://openid.net/specs/openid-connect-discovery-1_0.html), IDP can use more than one algorithm to sign tokens which can be indicated by id_token_encryption_alg_values_supported field from .well-known endpoint. Our IDP is doing the same. (screenshot attached for your reference).
So technically, IDP can use either of these algorithms to sign the token.
With current implementation of OidcIdTokenDecoderFactory, it's not possible to supply (through jwsAlgorithmResolver or any other means) both the algorithms to be used by underlying NimbusJwtDecoder.
I'll try to create a minimal sample to test and reproduce and will attached the details to this ticket but hopefully, with the info. I specified above and the screenshot I've attached from our IDP's .well-known endpoint, it will be helpful to understand the issue.
Comment From: jgrandja
@jigneshshukla I understand that the provider supports RS256 and ES256, however, an ID Token can only be signed by one algorithm for a client. For example, if client-A expects RS256 and client-B expects ES256, then the logic within the custom jwsAlgorithmResolver can be implemented based on this.
Comment From: jigneshshukla
@jgrandja - in our case, IDP is alternating between RS256 and ES256 per request from same client. Initially, when we saw the issue, my first thought was: "Does OpenID spec says anywhere that only one algorithm needs to be used for signing ID token for given client" and so tried to look into the spec, but couldn't clearly get anywhere such mention. Rather, I did find that id_token_signing_alg_values_supported is an Array so technically, it can have more than one algorithms. Based on that, I felt to open this issue.
Comment From: jgrandja
That is unusual because as mentioned in this comment the id_token_signed_response_alg should be configured as part of the client registration and should not be signed by another algorithm during the flow. It seems to me the provider has implemented off-spec. Can you please let me know which provider you are using?
Comment From: jgrandja
@jigneshshukla
I did find that id_token_signing_alg_values_supported is an Array so technically, it can have more than one algorithms
id_token_signing_alg_values_supported is not the same as id_token_signed_response_alg
Comment From: jigneshshukla
@jgrandja Ah ! I see what you're saying. So even though the id_token_signing_alg_values can indicate multiple algorithms, id_token_signed_response_alg must indicate only one algorithm which will be used for signing ID token for a given client. Is that correct understanding?
Comment From: jgrandja
Yes, that's correct. Please review the spec (link in comment) to understand it better.
I believe this is not an issue on our end so I think we can close it.
Comment From: jigneshshukla
@jgrandja - how do I pull the id_token_signed_response_alg from ClientRegistration metadata? I'm looking at org.springframework.security.oauth2.client.registration.ClientRegistration class which is used to represent individual client registration. And I don't see any direct element under it which represents metadata for given client. There is one ProviderDetails which has configurationMetadata map. However, that didn't give me the id_token_signed_response_alg. Also, as per the documentation of ClientRegistration.ProviderDetails (at https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/oauth2/client/registration/ClientRegistration.ProviderDetails.html), it seems it expected to "Returns a Map of the metadata describing the provider's configuration".
Do you mean we need to implement that logic explicitly in the AlgorithmResolver using clientRegistration object to:
- Identify the client metadata endpoint from provider configuration
- invoke the metadata endpoint to pull the client specific configuration data from provider
- use id_token_signed_response_alg if available as jwsAlgorithm; else, use RS256 as default
Regarding your questions on what provider we're using", we're actually having our inhouse provider implementation. However, I've tried to reproduce the same issue with auth0 also by creating a test application and explicitly setting algorithm to HS256 (auth0 supports both RS256 and HS256). I didn't see any alternation of the signing algorithm with auth0 (which is a good sign and aligns with the spec as per your explanation), however, I still didn't get id_token_signed_response_alg value back when I inspect the ClientRegistration object.
Comment From: jgrandja
@jigneshshukla
how do I pull the
id_token_signed_response_algfrom ClientRegistration metadata?
OpenID Connect Dynamic Client Registration 1.0 has not been implemented and therefore id_token_signed_response_alg is not available in ClientRegistration.
Although it's not implemented, it would not help, because:
IDP is alternating between RS256 and ES256 per request from same client
The in-house provider implementation has implemented off-spec so this issue should be resolved with the provider instead.
The ID Token signing algorithm for a specific client should be known at configuration time so a jwsAlgorithmResolver can be configured correctly. But because the provider alternates the signing algorithm per request per client, you will need to somehow implement the custom logic in the jwsAlgorithmResolver to meet your requirements or log an issue with the provider to update to comply per spec.
I'll close this issue since this is directly related to the provider implementation.
Comment From: jigneshshukla
@jgrandja - to clarify my question further:
I understand with our inhouse IDP, we can't use the id_token_signed_response_alg as the IDP is doing alternation between two different algorithms. That is something I'll take it back to IDP team to get it compliant.
I was trying to actually use that with auth0 which I've setup with my personal account separately and see how can I get id_token_signed_response_alg from ClientRegistration metadata to identify signing algorithm used by auth0 for ID token signing.
Did you mean it's not available in ClientRegistration because OpenID Connect Dynamic Client Registration implementation is not yet implemented in spring security oauth?
Comment From: jgrandja
Did you mean it's not available in ClientRegistration because OpenID Connect Dynamic Client Registration implementation is not yet implemented in spring security oauth?
Correct