Description
Describe the bug
I specifically want to use WebClientReactiveClientCredentialsTokenResponseClient because it provides WebClient to integrate with Okta api with client credentials private_key_jwt. Okta's /v1/token url needs client_assertion_type of urn:ietf:params:oauth:client-assertion-type:jwt-bearer, grant type as client_credentials and authentication method as PRIVATE_KEY_JWT.
To Reproduce
I would like to retrieve access token via client_credentials private_key_jwt flow through Spring Boot WebClient in-memory solution.
Upon debugging, client_id gets added as a result of which the body consists client_assertion, client_assertion_type, scope,grant_type and client_id due to AbstractWebClientReactiveOAuth2AccessTokenResponseClient class populateTokenRequestBody() private method.
I tried to manually integrate with Okta v1/token url through postman with client_assertion value retrieved from jwksResolver and I do get a valid Bearer token.
Expected behavior
I get below error
{
"errors": [
{
"status": "500",
"title": "INTERNAL_SERVER_ERROR",
"detail": "[invalid_request] Cannot supply multiple client credentials. Use one of the following: credentials in the Authorization header, credentials in the post body, or a client_assertion in the post body."
}
]
}
I have the below bean configurations.
@Bean
ReactiveClientRegistrationRepository clientRegistrations() {
ClientRegistration registration = ClientRegistration
.withRegistrationId("OktaExample")
.tokenUri("https://{oktaDomain}/oauth2/v1/token")
.clientId(clientId)
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
.scope(scope)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
return new InMemoryReactiveClientRegistrationRepository(registration);
}
@Bean
public ReactiveOAuth2AuthorizedClientService authorizedClientService(
ReactiveClientRegistrationRepository clientRegistrations) {
return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrations);
}
@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrations,
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
return configureHttpProxy(
new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
clientRegistrations,
authorizedClientService
));
}
private AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager configureHttpProxy(AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
Function<ClientRegistration, JWK> jwkResolver = client -> {
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.build();
};
WebClientReactiveClientCredentialsTokenResponseClient tokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
tokenResponseClient.addParametersConverter(new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
tokenResponseClient.setWebClient(
WebClient.builder().build()
);
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider= ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(clientCredentialsGrantBuilder ->
clientCredentialsGrantBuilder.accessTokenResponseClient(tokenResponseClient))
.build();
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
@Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("OktaExample");
return WebClient.builder()
.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(oauth2Client);
})
.build();
}