Describe the bug

I use ping as oidc provider. When I try to do a token exchange from springboot application, I got a "[invalid_request]" from ping.

When I drill down a little bit, I end up on the field value of the subject_token_type:

https://github.com/spring-projects/spring-security/blob/7030a62c76b0a0588ac5b99d605bbeabb8d4d783/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java#L114

basically, I got a jwt access token, this class make the subject_token_type to "jwt" format and ping won't accept this value and wait for the access token type ( urn:ietf:params:oauth:token-type:access_token )

The RFC do not specify you must use a "jwt" type in the case of a jwt access token, and it specify that "access_token" type is OK even if your access token is jwt. ( https://datatracker.ietf.org/doc/html/rfc8693#name-token-type-identifiers ) so I guess the certification tool do not check that, and not supporting the 'jwt' type is fine at this level.

To Reproduce

Need to use a ping server to bootstrap the token exchange connection and use it.

Expected behavior

Is there a way to "force" the use of access_token type instead of jwt ?

Sample

-

Comment From: sjohnr

@GregoireW thanks for reaching out!

Is there a way to "force" the use of access_token type instead of jwt ?

Generally, we prefer questions be asked on stack overflow. However, given that this question could require discussion that might require opening an issue, I'll respond directly here.

Yes, it is possible to customize the OAuth 2.0 Access Token Request for the token-exchange grant (RFC 8693). The Token Exchange Grant is discussed in the reference docs, specifically Customizing Request Parameters for Token Exchange.

One way to do this would be to override parameters with setParametersConverter, like so:

@Bean
public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> accessTokenResponseClient() {
    RestClientTokenExchangeTokenResponseClient accessTokenResponseClient =
        new RestClientTokenExchangeTokenResponseClient();
    accessTokenResponseClient.setParametersConverter(grantRequest -> {
        LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, "urn:ietf:params:oauth:token-type:access_token");

        return parameters;
    });

    return accessTokenResponseClient;
}

Note that simply publishing this component as a bean is typically enough to plug it into the framework. Please review the OAuth2 overview page and authorization grants page in detail as all of this information is available there.

I'm going to close this issue as answered. However, if you have any additional thoughts or feedback on whether this customization meets your needs or the documentation around it, please add an additional comment and we can keep discussing. My hope is that this information is easy to find and understand and that the framework provides the flexibility you need, but we value any feedback you have on it.

Comment From: GregoireW

Ah! thank you!

I was looking on the default converter & co and face ".addAll" methods (DefaultTokenXxx class). Did not go down the rabbit hole to the documentation nor on the request.

Moving to the RestClientTokenExchangeTokenResponseClient did the trick.

I did follow a documentation that was explicitly referencing the DefaultTokenxxx class, so I was not aware there was a different implementation and a way to update parameters.

I guess when the default will not be the default anymore it will be easier to find the documentation.

As of today, the bean declaration is needed so there is no need to change anything. If there is a change to create this bean automatically from the registration then it would be nice to have a way to declare such converter either via a bean or via configuration, but again, as today we have to declare it, I guess it is not a big deal.

Thank you

Comment From: sjohnr

I did follow a documentation that was explicitly referencing the DefaultTokenxxx class, so I was not aware there was a different implementation and a way to update parameters.

Can you share a link to the documentation you were reading? Was it an older version of the docs?

I guess when the default will not be the default anymore it will be easier to find the documentation.

Also, please note that the javadoc and source code for DefaultTokenExchangeTokenResponseClient refer to RestClientTokenExchangeTokenResponseClient in the deprecation notice.

As of today, the bean declaration is needed so there is no need to change anything. If there is a change to create this bean automatically from the registration then it would be nice to have a way to declare such converter either via a bean or via configuration, but again, as today we have to declare it, I guess it is not a big deal.

Sorry, I'm not quite following yet. Regarding "it would be nice to have a way to declare such converter either via a bean or via configuration," can you clarify with an example what you think would be an improvement?

Comment From: GregoireW

Can you share a link to the documentation you were reading? Was it an older version of the docs?

I guess it was an early 6.3 version. The documentation point to a "addAll" method.

Regarding the configuration

Again, as it is today it is not a big deal.

Now, let say you have a configuration:

spring:
  security:
    oauth2:
      client:
        registration:
          exchange:
            provider: xxx
            client-id: ${CLIENT_ID:1234}
            client-secret: ${CLIENT_SECRET:1234}
            authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
            client-authentication-method: client_secret_basic
            scope:
              - openid
              - profile

Hypothetically Spring Security one day will define that this declaration is enough to create a new RestClientTokenExchangeTokenResponseClient In such case, I would expect we could define kind of TokenExchangeParameterConverter that would automatically be added to the RestClientTokenExchangeTokenResponseClient. Another possibility would be to be able to add parameters directly in the properties like:

spring:
  security:
    oauth2:
      client:
        registration:
          exchange:
            provider: xxx
            client-id: ${CLIENT_ID:1234}
            client-secret: ${CLIENT_SECRET:1234}
            authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
            client-authentication-method: client_secret_basic
            scope:
              - openid
              - profile
            additional-parameters:
              xxxx: yyyy

Again it is just an hypothetical

Comment From: sjohnr

Hypothetically Spring Security one day will define that this declaration is enough to create a new RestClientTokenExchangeTokenResponseClient

Thanks, that helps me understand what you're thinking about.

Another possibility would be to be able to add parameters directly in the properties

This idea (or something like it) has come up more than once recently, and could be worth pursuing. It has also come up before (for example, gh-10452) but was rejecting since it was extremely limited to static properties only. If applied only to adding request parameters for token requests, it does not add enough flexibility. However, I think adding a way to somehow associate additional parameters with the ClientRegistration could be beneficial for multiple use cases. I will discuss with the team and may open an issue after discussion.

Comment From: GregoireW

This idea (or something like it) has come up more than once recently, and could be worth pursuing. It has also come up before (for example, gh-10452) but was rejecting since it was extremely limited to static properties only. If applied only to adding request parameters for token requests, it does not add enough flexibility. However, I think adding a way to somehow associate additional parameters with the ClientRegistration could be beneficial for multiple use cases. I will discuss with the team and may open an issue after discussion.

The issue with this approach is "to add or not to add". My guess would be to create 2 parameters "add-properties", "set-properties" but ... what does it means ? for token exchange is pretty simple you have only 1 call to the token endpoint. For code flow, does it make sense to add parameter on authentication endpoint or token endpoint ?
So... not great... ok for my usecase it would be easier, but I totally understand it would not be implemented this way.