Expected Behavior Be able to extend the AbstractWebClientReactiveOAuth2AccessTokenResponseClient for custom AuthorizationGrantType implementations not just the four default ones implemented in the spring security framework.

Current Behavior

The current implementation of the oauth2 AbstractWebClientReactiveOAuth2AccessTokenResponseClient uses the type T extends AbstractOAuth2AuthorizationGrantRequest which implies the ability to extend the AbstractOAuth2AuthorizationGrantRequest and extend the AbstractWebClientReactiveOAuth2AccessTokenResponseClient to implement a custom authorization grant. The AbstractWebClientReactiveOAuth2AccessTokenResponseClient however has a package private constructor which restricts the ability to extend the mentioned class unless one puts the class in the org.springframework.security.oauth2.client.endpoint in their codebase.

Context The OAuth2 spec allows for custom implementations of the OAuth2 grants by defining a grant type as specified in OAuth2 RFC 6749 Section 4.5: Extension Grants.

Current implementation of the AuthorizationGrantType allows for custom grant types to be defined and the extension of AbstractOAuth2AuthorizationGrantRequest allows that as well.

However the inability to extend the AbstractWebClientReactiveOAuth2AccessTokenResponseClient leaves one with only one choice is to either duplicate the implementation in the afformentioned class, or write ones own implementation. Which is quite annoying when the base is already present in the framework code.

The request to allow for extension of this class has be done before i.e. https://github.com/spring-projects/spring-security/issues/10836 but with a failed mention to provide for customisation it was declined. However there was no mention or thought of custom grant type support.

It would be great to be able to create a custom extension of the said class in our own package structure rather than having to either reimplement the internals of the AbstractWebClientReactiveOAuth2AccessTokenResponseClient or place the new client into the org.springframework.security.oauth2.client.endpoint package.

Comment From: sjohnr

Hi @kkondratov, thanks for reaching out!

I have reviewed the existing implementation(s) and agree that there is quite a bit of boilerplate code required to implement the ReactiveOAuth2AccessTokenResponseClient interface. Being able to extend AbstractWebClientReactiveOAuth2AccessTokenResponseClient is one possible solution.

However, I don't believe the abstract class was ever intended to be made public, and may have only been made public due to unavoidable circumstances during refactoring that I don't have full context on. The constructor remaining package-private seems to confirm that this was the intention. Further, reviewing the class I can tell you that I would not prefer to open up this class to extension, as it will continue to be beneficial to keep this class internal to the framework and not allow users to extend it. (I understand that users can circumvent this by using the package-collision workaround but this is clearly not intended either.)

Having said that, the existing abstract class is already suitable for a generic implementation, except that it is not possible to construct a plain instance and customize the headersConverter and parametersConverter like you can for any other subclass. So I propose we create a new public class like the following to support custom grant types:

public final class WebClientReactiveOAuth2AccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest>
        extends AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T> {

    @Override
    ClientRegistration clientRegistration(T grantRequest) {
        return grantRequest.getClientRegistration();
    }

    @Override
    Set<String> scopes(T grantRequest) {
        return grantRequest.getClientRegistration().getScopes();
    }

}

which could then be used like so:

public class MyGrantRequest extends AbstractOAuth2AuthorizationGrantRequest {

    private final String assertion;

    protected MyGrantRequest(ClientRegistration clientRegistration, String assertion) {
        super(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:my-grant-type"), clientRegistration);
        this.assertion = assertion;
    }

    public String getAssertion() {
        return this.assertion;
    }

}

static ReactiveOAuth2AccessTokenResponseClient<MyGrantRequest> myTokenResponseClient() {

    WebClientReactiveOAuth2AccessTokenResponseClient<MyGrantRequest> tokenResponseClient =
            new WebClientReactiveOAuth2AccessTokenResponseClient<>();

    tokenResponseClient.addParametersConverter((grantRequest) -> {
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.add(OAuth2ParameterNames.ASSERTION, grantRequest.getAssertion());

        return parameters;
    });

    return tokenResponseClient;
}

What do you think of this proposal as an alternative to opening up the existing abstract class?

Comment From: kkondratov

@sjohnr Thank you for clarifying that the intent of the existing class was keeping it closed off for extension.

Indeed the current implementation AbstractWebClientReactiveOAuth2AccessTokenResponseClient contains a large boilerplate which can be reused, hence my question/suggestion.

Your proposal to add a class that contains the boilerplate and let the internals be customizable is exactly what we've done by using the package-collision workaround with the existing AbstractWebClientReactiveOAuth2AccessTokenResponseClient.

The proposed solution indeed solves the issue of implementing custom grant types and I agree with the solution.

Reason for edit: I misunderstood the proposed implementation initially hence the deletion of the other code.

Comment From: sjohnr

Thanks for confirming that this suggestion would work for you! Would you be interested in submitting a PR with the proposed WebClientReactiveOAuth2AccessTokenResponseClient and some tests? I'm happy to help guide you if you need help, as I'm currently adding an implementation for Token Exchange Grant right now.

Comment From: kkondratov

@sjohnr I'd be happy and interested in submitting a PR. Will need to read up on the guidelines and the procedure regarding the spring projects.

Comment From: sjohnr

Thanks @kkondratov! If you weren't aware, you can read about that here.

As far as adding tests, take a look at WebClientReactiveTokenExchangeTokenResponseClientTests which I added yesterday. I think it turned out fairly well, and looks similar to the test class for the non-reactive client. See also WebClientReactiveJwtBearerTokenResponseClientTests, though it looks slightly different than the corresponding non-reactive version.

Since jwt-bearer is a very simple grant type, my suggestion would be to re-use the existing JwtBearerGrantRequest but configure the generic client and test it as if you were implementing the jwt-bearer grant type as custom. Feel free to use or discard that suggestion. :wink:

Comment From: sjohnr

Hi @kkondratov, have you had a chance to work on this? If not, no problem.