Summary OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders does not work properly if client credentials contain special characters. From RFC 6749:

Clients in possession of a client password MAY use the HTTP Basic authentication scheme as defined in [RFC2617] to authenticate with the authorization server. The client identifier is encoded using the "application/x-www-form-urlencoded" encoding algorithm per Appendix B, and the encoded value is used as the username; the client password is encoded using the same algorithm and used as the password.

Actual Behavior The client with client name or password containing special characters cannot login. The provider returns exception.

Expected Behavior The client with client name or password containing special characters can be authenticated.

Configuration Sample spring.security.oauth2.client.registration.sth.client-secret = sthUI=+2~fubar

Where org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(ClientRegistration)

Related This is related to https://github.com/spring-projects/spring-security-oauth/issues/1826

Comment From: eleftherias

Thanks for reaching out @DrZ7. Which provider are you using when you see the exception? Could you also share the stacktrace here?

Comment From: DrZ7

This ticket is the counterpart to https://github.com/spring-projects/spring-security-oauth/issues/1826... Our provider works according to specification, it decodes client name and password. Replacing headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); with headers.setBasicAuth(URLEncoder.encode(clientRegistration.getClientId(), StandardCharsets.UTF_8), URLEncoder.encode(clientRegistration.getClientSecret(), StandardCharsets.UTF_8)); fixes the issue...

stack with special character password and without encoding:

org.springframework.security.oauth2.client.ClientAuthorizationException: [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: 401 : [no body] at org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider.getTokenResponse(ClientCredentialsOAuth2AuthorizedClientProvider.java:96) at org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider.authorize(ClientCredentialsOAuth2AuthorizedClientProvider.java:85) at org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClientProvider.authorize(DelegatingOAuth2AuthorizedClientProvider.java:71) at org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager.authorize(AuthorizedClientServiceOAuth2AuthorizedClientManager.java:142)

Comment From: jzheaux

I believe this can be addressed by supplying a custom headers converter:

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
    ClientRegistrationRepository registrations,
    OAuth2AuthorizedClientRepository clients) {
  Converter<OAuth2ClientCredentialsGrantRequest, HttpHeaders> headersConverter = (request) -> {
      ClientRegistration registration = request.getClientRegistration();
      HttpHeaders headers = new HttpHeaders();
      headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
      headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
      headers.setBasicAuth(
          URLEncoder.encode(registration.getClientId(), StandardCharsets.UTF_8), 
          URLEncoder.encode(registration.getClientSecret(), StandardCharsets.UTF_8));
      return headers;
  };
  OAuth2ClientCredentialsGrantRequestEntityConverter entityConverter =
      new OAuth2ClientCredentialsGrantRequestEntityConverter();
  entityConverter.setHeadersConverter(headersConverter);
  DefaultClientCredentialsTokenResponseClient client = 
      new DefaultClientCredentialsTokenResponseClient();
  client.setRequestEntityConverter(entityConverter);
  OAuth2AuthorizedClientProvider authorizedClientProvider = 
      OAuth2AuthorizedClientProviderBuilder.builder()
          .clientCredentials((clientCredentials) -> clientCredentials
              .accessTokenResponseClient(client))
          .build();
  DefaultOAuth2AuthorizedClientManager authorizedClientManager =
      new DefaultOAuth2AuthorizedClientManager(registrations, clients);
  authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  return authorizedClientManager;
}

Perhaps @jgrandja can provide a simpler configuration.

The spec does seem to indicate that URL encoding should be performed by default. If so, then applications using a non-compliant provider would need to provide a headers converter instead. @jgrandja do you think the default client behavior should change?

This question has cropped up on the authorization server side of things in a couple of different places recently.

Comment From: jgrandja

@DrZ7 The configuration that @jzheaux provided is correct and will work for the compliant provider you are using. I'm assuming you already applied a similar custom configuration and it's working?

I agree that OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders() should be updated to comply with the spec, but as @OrangeDog mentioned in this comment, applying the fix now may break existing applications that are using non-compliant providers.

Therefore, I've scheduled this fix for Spring Security 6.0 (Update: Spring Security 5.6).

Comment From: DrZ7

At the moment we are using our own OAuth2ClientCredentialsGrantRequestEntityConverter b/c setHeadersConverter is not yet available.

Comment From: jgrandja

@DrZ7 Regarding my last comment:

applying the fix now may break existing applications that are using non-compliant providers. Therefore, I've scheduled this fix for Spring Security 6.0.

We're going to apply this fix in 5.5 since this is a bug. We'll come up with a strategy that does not break existing applications.

Comment From: AT92

Hello together, had the same issue with spring-security-oauth2-client-5.6.0 again. After downgrading to 5.5.3 the problem was resolved, so I think the bug came again with new release.

Comment From: sjohnr

had the same issue with spring-security-oauth2-client-5.6.0 again.

Hi @AT92. Can you describe what you mean by this? Are you saying it did not encode correctly, or it is now encoding and you don't want it to?

Comment From: rynoj

Hi, we're facing the same. After updating to 5.6.0 credentials are being encoded again and we don't want it to (since the do contain some special characters)

Comment From: sjohnr

@rynoj that is correct. See this comment on #10018. This change is permanent starting with 5.6. See this comment if you need help working around the issue with a non-compliant provider.

Also note that if you're using reactive, we've merged a change for #10130 which simplifies customizing headers beyond what the stackoverflow post linked above demonstrates.

Comment From: rynoj

Ah thanks @sjohnr missed those comments! I'll have a look at the provided workarounds.