Please provide an API to enable OAuth2AuthorizationCodeGrantFilter to resolve the Authentication, given the OAuth2AuthorizationCodeAuthenticationToken. If present, this would be used to obtain the Authentication used to: - construct the OAuth2AuthorizedClient; and - save it via OAuth2AuthorizedClientRepository

E.g.,

public void setAuthenticationResolver(Function<OAuth2AuthorizationCodeAuthenticationToken, Authentication> resolver) {
         this.authenticationResolver = resolver;
}

This would be used in processAuthorizationResponse(...) as follows:

    Authentication currentAuthentication;
    if (authenticationResolver != null) {
        currentAuthentication = authenticationResolver .apply(authenticationResult);
    else {  
            // support the existing behaviour
        currentAuthentication = this.securityContextHolderStrategy.getContext().getAuthentication();
    }
    String principalName = (currentAuthentication != null) ? currentAuthentication.getName() : "anonymousUser";
    OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
                authenticationResult.getClientRegistration(), principalName, authenticationResult.getAccessToken(),
                authenticationResult.getRefreshToken());
    this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, currentAuthentication, request,
                response);

Current Behavior

The current behaviour uses the securityContextHolderStrategy to resolve the authentication.

Context

This is related to https://stackoverflow.com/questions/75254595

I use JavaMail to send email via SMTP and monitor replies via IMAP for multiple accounts. This uses username and password authentication. I now need to add support for OAuth2, using the code grant flow.

I want to associate the email address of the user who granted access to their mailbox with the corresponding OAuth2AuthorizedClient, in order to get the appropriate access token when connecting to a mailbox. The current behaviour of OAuth2AuthorizationCodeGrantFilter is insufficient, as it uses the securityContextHolderStrategy to determine the Authentication. In my case, this is the user logged into the webapp, whereas I want it to be the email address of the resource owner.

To work around this, I've duplicated OAuth2AuthorizationCodeGrantFilter and added:

    private Authentication getAuthentication(OAuth2AuthorizationCodeAuthenticationToken authenticationResult) {
        ClientRegistration clientRegistration = authenticationResult.getClientRegistration();
        JwtDecoder decoder = tokenDecoderFactory.createDecoder(clientRegistration);
        String idToken = (String) authenticationResult.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
        if (StringUtils.isEmpty(idToken)) {
            OAuth2Error error = new OAuth2Error(
                    "invalid_id_token", "Missing (required) ID Token in Token Response for Client Registration: "
                                        + clientRegistration.getRegistrationId(), null);
            throw new OAuth2AuthenticationException(error, error.toString());
        }
        Jwt jwt = decoder.decode(idToken);
        String email = jwt.getClaimAsString("email");
        if (StringUtils.isEmpty(email)) {
            OAuth2Error error = new OAuth2Error(
                    "invalid_id_token", "Missing email in Token Response for Client Registration: "
                                        + clientRegistration.getRegistrationId(), null);
            throw new OAuth2AuthenticationException(error, error.toString());
        }
        return new PreAuthenticatedAuthenticationToken(email, null, Collections.emptyList());
    }

This is invoked within processAuthorizationResponse(...) follows:

        Authentication authentication = getAuthentication(authenticationResult);
        String principalName = authentication.getName();
        OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
                authenticationResult.getClientRegistration(), principalName, authenticationResult.getAccessToken(),
                authenticationResult.getRefreshToken());
         this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, authentication, request, response);

To override the default code grant filter, I've added:

    <security:custom-filter before="OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER"
                                         ref="myOauth2AuthorizationCodeGrantFilter"/>