Summary

There is no way currently to pass custom form parameters as part of OAuth2ClientCredentialsGrantRequestEntityConverter: https://github.com/spring-projects/spring-security/blob/master/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverter.java

Some IDPs (Auth0 for example) require an "audience" value.

Comment From: jgrandja

@fritzdj This is actually possible. You would need to supply DefaultClientCredentialsTokenResponseClient.setRequestEntityConverter() with an implementation of Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>>, similar to how OAuth2ClientCredentialsGrantRequestEntityConverter is implemented.

I'm going to close this issue as answered.

Comment From: fritzdj

@jgrandja, thanks for the quick response. How is it possible to do that though? We are not seeing a way to update that / the class that uses it is not autowired.

Comment From: jgrandja

Here is a sample config

@Configuration
public class WebClientConfig {

    @Bean
    WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {
        // TODO Obtain your custom Converter
        Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> customRequestEntityConverter = null;

        DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient = 
                new DefaultClientCredentialsTokenResponseClient();
        clientCredentialsTokenResponseClient.setRequestEntityConverter(customRequestEntityConverter);

        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = 
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(
                        clientRegistrationRepository, authorizedClientRepository);
        oauth2.setClientCredentialsTokenResponseClient(clientCredentialsTokenResponseClient);

        return WebClient.builder()
                .apply(oauth2.oauth2Configuration())
                .build();
    }
}

Comment From: fritzdj

Thanks again @jgrandja, this is great. What about if we are not using WebFlux?

Comment From: fritzdj

To be more specific, we want to use the @RegisteredOAuth2AuthorizedClient annotation.

Comment From: jgrandja

@fritzdj In that case, you can use this @Configuration

@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final ClientRegistrationRepository clientRegistrationRepository;
    private final OAuth2AuthorizedClientRepository authorizedClientRepository;

    public WebConfig(ClientRegistrationRepository clientRegistrationRepository,
                        OAuth2AuthorizedClientRepository authorizedClientRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
        this.authorizedClientRepository = authorizedClientRepository;
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient =
                new DefaultClientCredentialsTokenResponseClient();
        clientCredentialsTokenResponseClient.setRequestEntityConverter(
                new CustomClientCredentialsGrantRequestEntityConverter());

        OAuth2AuthorizedClientArgumentResolver authorizedClientArgumentResolver =
                new OAuth2AuthorizedClientArgumentResolver(this.clientRegistrationRepository, this.authorizedClientRepository);
        authorizedClientArgumentResolver.setClientCredentialsTokenResponseClient(clientCredentialsTokenResponseClient);
        argumentResolvers.add(authorizedClientArgumentResolver);
    }

    private static class CustomClientCredentialsGrantRequestEntityConverter implements Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> {
        private final Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> defaultRequestEntityConverter =
                new OAuth2ClientCredentialsGrantRequestEntityConverter();

        @Override
        public RequestEntity<?> convert(OAuth2ClientCredentialsGrantRequest source) {
            return this.defaultRequestEntityConverter.convert(source);
        }
    }
}

Comment From: jgrandja

@fritzdj I just added issue #6572 to allow for an easier configuration from the one I provided. If you're interested in submitting a PR for this I'd be happy to guide you through the process?

Comment From: fritzdj

@jgrandja, I would love to give that a shot. I see the contributor guidelines for the project so I will try this out.

Comment From: wangyue82lf

Hi @jgrandja , I have same the issue, but we need to use HttpSecurity.oauth2Login() custom header for client_credential. But I found TokenEndpointConfig.accessTokenResponseClient() must use OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>.

I want some like the one below. Do you have any suggest?

`

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/getToken")
        .permitAll()
        .anyRequest()
        .authenticated()
        .and()
        .oauth2Login(oauth2 ->
                             oauth2.authorizationEndpoint(authorization -> authorization.authorizationRequestResolver(
                                           new CustomAuthorizationRequestResolver(this.clientRegistrationRepository(), "/getToken")))
                                   .redirectionEndpoint(redirection -> redirection.baseUri("/getToken"))
                                   .tokenEndpoint(token -> token.accessTokenResponseClient(this.accessTokenResponseClient()))
                                   .userInfoEndpoint(userInfo -> System.out.println(userInfo)));
    return http.build();
}

@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient() {
    DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient = new DefaultClientCredentialsTokenResponseClient();
    clientCredentialsTokenResponseClient.setRequestEntityConverter(new CustomRequestEntityConverter());
    OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
    tokenResponseHttpMessageConverter.setAccessTokenResponseConverter(new CustomTokenResponseConverter());
    RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(), tokenResponseHttpMessageConverter));
    restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
    clientCredentialsTokenResponseClient.setRestOperations(restTemplate);

    OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient = clientCredentialsTokenResponseClient;
    return accessTokenResponseClient;
}`

Comment From: jgrandja

@wangyue82lf See the following references on how to customize DefaultClientCredentialsTokenResponseClient:

Comment From: wangyue82lf

@wangyue82lf See the following references on how to customize DefaultClientCredentialsTokenResponseClient:

@jgrandja Thank you for your help. I'm using JDBC implementation for saving token. But after getting token response. The database did not save refresh token, although I returned refresh token. I need to use refresh as below. but it's not working. I also see the document. How do I make refresh work?

`

@Bean
public ClientRegistration clientRegistration() {
    return ClientRegistration
            .withRegistrationId("Custom")
            .tokenUri("XXXX")
            .clientId("Custom")
            .clientSecret("XXXX")
            .registrationId("Custom")
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .build();
}

@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
    // keep save token in memory
    return new InMemoryClientRegistrationRepository(this.clientRegistration());
}

@Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService(JdbcOperations jdbcOperations) {
    // keep save token in DB
    return new JdbcOAuth2AuthorizedClientService(jdbcOperations, this.clientRegistrationRepository());
}

@Bean
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests(authorizeRequests -> authorizeRequests.antMatchers(HttpMethod.POST, "/getToken").authenticated())
        .headers()
        .httpStrictTransportSecurity()
        .disable()
        .and()
        .oauth2Login();
    return http.build();
}


@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
                                                             OAuth2AuthorizedClientRepository authorizedClientRepository,
                                                             OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
                                                             CustomOAuth2ClientCredentialsTokenResponseClient credentialsClient,
                                                             CustomOAuth2ClientRefreshTokenResponseClient refreshClient) {

    OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
            .builder()
            .clientCredentials(clientCredentials -> clientCredentials.accessTokenResponseClient(credentialsClient))
            .refreshToken(refreshToken -> refreshToken.accessTokenResponseClient(refreshClient))
            .build();

    AuthorizedClientServiceOAuth2AuthorizedClientManager manager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
            clientRegistrationRepository,
            oAuth2AuthorizedClientService);
    manager.setAuthorizedClientProvider(authorizedClientProvider);
    return manager;
}



@Bean
public CustomOAuth2ClientCredentialsTokenResponseClient credentialsClient(client client) {
    return new CustomOAuth2ClientCredentialsTokenResponseClient(client);
}

@Bean
public CustomOAuth2ClientRefreshTokenResponseClient refreshClient(client client) {
    return new CustomOAuth2ClientRefreshTokenResponseClient(client);
}

`