Skip to content

Error when using private_key_jwt authentication method #13476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
benfonty opened this issue Jul 10, 2023 · 6 comments
Closed

Error when using private_key_jwt authentication method #13476

benfonty opened this issue Jul 10, 2023 · 6 comments
Assignees
Labels
for: stackoverflow A question that's better suited to stackoverflow.com in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)

Comments

@benfonty
Copy link

Describe the bug
Upgrading our codebase to Spring Boot 3.1.1, we got an eror message when calling Salesforce API.

This class supports client_secret_basic, client_secret_post, and none by default. Client [salesforce-client] is using [org.springframework.security.oauth2.core.ClientAuthenticationMethod@3b4abc2b] instead. Please use a supported client authentication method, or use setRequestEntityConverter to supply an instance that supports [org.springframework.security.oauth2.core.ClientAuthenticationMethod@3b4abc2b]

We use private_key_jwt authentication method.

It seems that this is a consequence of #13240

We found a workaround bypassing the decorator:

DefaultJwtBearerTokenResponseClient jwtBearerTokenResponseClient = new DefaultJwtBearerTokenResponseClient();
jwtBearerTokenResponseClient.setRequestEntityConverter(new JwtBearerGrantRequestEntityConverter());
jwtBearerOAuth2AuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerTokenResponseClient);

After bypassing the decorator it works well, as before the modification.

To Reproduce

Here is the configuration of our security:

 @Bean
 public OAuth2AuthorizedClientManager authorizedJwtBearerClientManagerJwt(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientService authorizedClientService) {

        JwtBearerOAuth2AuthorizedClientProvider jwtBearerOAuth2AuthorizedClientProvider =
                new JwtBearerOAuth2AuthorizedClientProvider();
        jwtBearerOAuth2AuthorizedClientProvider.setJwtAssertionResolver(this::resolveJwtAssertion);

        bypassClientAuthenticationMethodValidatingRequestEntityConverter(jwtBearerOAuth2AuthorizedClientProvider);

        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .provider(jwtBearerOAuth2AuthorizedClientProvider)
                        .build();

        AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
                new AuthorizedClientServiceOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

 @Bean
 public WebClient webClient(
                               @Qualifier("authorizedJwtBearerClientManagerJwt")
                               OAuth2AuthorizedClientManager authorizedJwtBearerClientManagerJwt) {
     
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedJwtBearerClientManagerJwt);
        oauth2Client.setDefaultClientRegistrationId("salesforce-client");

        return WebClient.builder()
                .baseUrl(sfdcBaseUrl)
                .apply(oauth2Client.oauth2Configuration())
                .build();
    }

@benfonty benfonty added status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Jul 10, 2023
@jzheaux jzheaux self-assigned this Jul 10, 2023
@jzheaux jzheaux added in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) and removed status: waiting-for-triage An issue we've not yet triaged labels Jul 10, 2023
@jzheaux
Copy link
Contributor

jzheaux commented Jul 10, 2023

Thanks, @benfonty. This looks like an oversight. I've prioritized it for the next maintenance release.

@jzheaux
Copy link
Contributor

jzheaux commented Jul 12, 2023

Actually, I think my earlier analysis is incorrect.

JwtBearerGrantRequestEntityConverter does not by default support private_key_jwt. It requires further configuration, thus the error message.

To configure JwtBearerGrantRequestEntityConverter to honor private_key_jwt, you should use NimbusJwtClientAuthenticationParametersConverter like so:

JwtBearerGrantRequestEntityConverter requestEntityConverter = new JwtBearerGrantRequestEntityConverter();
requestEntityConverter.setParametersConverter(new NimbusJwtClientAuthenticationParametersConverter(this::keyLookup));

Where keyLookup is a strategy that you provide for discovering your Client's private key to use.

Or, if you don't need to use a JWT for client authentication as well, you can choose a method that is supported out of the box.

To summarize, you can address this error in one of two ways:

  1. Use a supported ClientAuthenticationMethod. As the error states, these are none, client_secret_basic, and client_secret_post.
  2. Configure JwtBearerGrantReqeustEntityConverter: For example, using NimbusJwtClientAuthenticationParametersConverter does the work of adding client_assertion in accordance with this JWT Bearer Grant Type spec.

@jzheaux jzheaux added status: waiting-for-feedback We need additional information before we can continue and removed type: bug A general bug labels Jul 12, 2023
@benfonty
Copy link
Author

Thank you for the feedback.

In the code I provided, this part:

JwtBearerOAuth2AuthorizedClientProvider jwtBearerOAuth2AuthorizedClientProvider = 
                   new JwtBearerOAuth2AuthorizedClientProvider();
jwtBearerOAuth2AuthorizedClientProvider.setJwtAssertionResolver(this::resolveJwtAssertion);

We set the method resolveJwtAssertion, whose responsibility is to build the jwt we used for authentication.

I have the feeling that the goal of the class JwtBearerOAuth2AuthorizedClientProvide was to handle private_key_jwt, and it does it pretty well because it is working well calling Salesforce's Jwt Bearer flow (I didn't check whether it adds all the required parameters but I assume so).

That's why I am a little confused about the fact that we have to use NimbusJwtClientAuthenticationParametersConverter whose goal seems to also provide a jwt parameter.

There must be something I didn't understand here, can you provide some clarification?

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jul 13, 2023
@jzheaux
Copy link
Contributor

jzheaux commented Jul 13, 2023

There are two assertions in the JWT Bearer grant type: The bearer assertion and the client assertion. The first is the grant and the second identifies the client.

JwtBearerOAuth2AuthorizedClientProvider#jwtAssertionResolver is for adding the bearer assertion.

NimbusJwtClientAuthenticationParametersConverter is for adding the client assertion.

This can be confusing, though, since the bearer assertion can be used to identify the client, making the client assertion redundant. This nuance is identified in the spec:

Authentication of the client is optional, as described in [Section 3.2.1] ...

So, if you are using jwtAssertionResolver to identify the client, no further client authentication is needed, and ClientAuthenticationMethod.NONE is the most correct.

On the other hand, if your bearer assertion does not identify the client, then you should add a client assertion as well. This is when you would use NimbusJwtClientAuthenticationParametersConverter.

@jzheaux jzheaux added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Jul 13, 2023
@benfonty
Copy link
Author

I just made some tests using ClientAuthenticationMethod.NONE and disabling the workaround we made and it seems to work properly.
Thank you for your clarification.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jul 17, 2023
@jzheaux jzheaux added for: stackoverflow A question that's better suited to stackoverflow.com and removed status: feedback-provided Feedback has been provided labels Jul 17, 2023
@jzheaux
Copy link
Contributor

jzheaux commented Jul 17, 2023

Glad you got it sorted out! Happy to have helped.

@jzheaux jzheaux closed this as completed Jul 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: stackoverflow A question that's better suited to stackoverflow.com in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)
Projects
None yet
Development

No branches or pull requests

3 participants