Expected Behavior

When a ClientAuthorizationRequiredException is thrown, a 401 response should be sent if X-Requested-With header equals XMLHttpRequest.

Current Behavior

Current implementation of OAuth2AuthorizationRequestRedirectFilter always redirect even when request was sent with X-Requested-With header.

Context

I'm trying to create a rest api that connect to an external oauth2 protected api and act on behalf of a user. For that I used oauth2client() and ServletOAuth2AuthorizedClientExchangeFilterFunction. But when I try to access the api with ajax and client is not authorized I am redirected to the oauth2 authorization page and it causes cors error because authorization code flow isn't compatible with ajax. It would be great if instead of redirecting it sent a 401 unauthorized response. As a workaround I created a custom filter that detects ClientAuthorizationRequiredException and send a 401 response but it should be implemented directly in spring security oauth2 client.

Comment From: jzheaux

Hi, @App13Pie, thanks for the suggestion.

If you can't use authorization code flow, how are you obtaining an access token in the first place? If you are wanting a 401, it sounds like you might need your API to be a resource server instead of a client.

Comment From: App13Pie

I have an api gateway that handle authentication with an oidc provider and relays the access token to downstream services. These services are resource servers, and use the oidc provider access token to authenticate and authorize users. One of my services need to access an external oauth2 protected API and act on behalf of the user. As I already have an oidc provider configured, I'm trying to use spring security oauth2client() to obtain an access token with authorization code flow, and relay that token to the external api. The problem I'm facing is when I try to access my API with an ajax request, I get redirected to the authorization server, but it is not possible to authorize a user in an ajax request because the consent screen is not displayed and it also causes a cors error. So I need that when I'm trying to access my api in an ajax request without having an access token stored in the session, the api returns a 401 error instead of redirecting directly to the authorization server. I also need that when I throw a ClientAuthorizationRequiredException from a custom webclient exchange filter function because the external api returned 401, my api also returns 401 error instead of redirecting to the authorization server.

Comment From: jzheaux

Thanks for the extra detail!

One of my services need to access an external oauth2 protected API and act on behalf of the user. As I already have an oidc provider configured, I'm trying to use spring security oauth2client() to obtain an access token with authorization code flow, and relay that token to the external API.

oauth2Client + Authorization Code is not designed to be used by services. There are other flows that are more natural for services like Client Credentials. If you want to use Authorization Code flow, you'd want to do that further upstream, for example in your gateway. Since oauth2Client supports multi-tenancy, you can configure the gateway for more than one authorization server.

This is sounding more like an architecture question at this point, and so I'm going to close this and recommend that you post any further commentary in a StackOverflow question. Please feel free to share the StackOverflow link here, and I'd be happy to continue taking a look.

Comment From: App13Pie

Okay so I'm gonna detail a bit more because to me it seems like a real issue and I might not be explaining it well enough. I have okta oauth2login setup at the gateway, the access token is then relayed to downstream services. Theses services are oauth2resourceserver and use the okta access token for authorization. One of my services need to access google drive api on behalf of the user to access some of the user's files, so I am forced to use Authorization Code flow. I have setup an oauth2client for google drive api and a webclient with ServletOAuth2AuthorizedClientExchangeFilterFunction. The problem is when accessing my api endpoint that calls google drive api using the webclient, if no access token is found for google drive, I am redirected to the okta authorization server, even in the context of an ajax request. As authorization cannot be handled through ajax request because the user would not get the consent screen, I should not get redirected if X-Requested-With equals XMLHttpRequest and instead get a 401 so that my frontend can then properly redirect the user to my service authorization endpoint for google drive. To solve this issue I created a filter similar to OAuth2AuthorizationRequestRedirectFilter :

@Component
public class OAuth2AuthorizationRequestUnauthorizedFilter extends OncePerRequestFilter implements Ordered {

    private final static int ORDER = Ordered.LOWEST_PRECEDENCE - 5;

    private final ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer();

    @Override
    public int getOrder() {
        return ORDER;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            filterChain.doFilter(request, response);
        } catch (Exception ex) {
            if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
                Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);

                ClientAuthorizationRequiredException authzEx = (ClientAuthorizationRequiredException) this.throwableAnalyzer
                        .getFirstThrowableOfType(ClientAuthorizationRequiredException.class, causeChain);

                if (authzEx != null) {
                    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    return;
                }
            }

            throw ex;
        }
    }

}

But it would be nice if it was implemented by default.