javaspringspring-bootspring-security

OAuth2 with Discord: OAuth2AuthorizationRequestRedirectFilter and OAuth2LoginAuthenticationFilter are stuck in a loop. requiresAuthentication is false


I'm trying to use Oauth2 with Discord as a provider. I have this configuration:

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig  {


    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.oauth2Login();

        http.authorizeHttpRequests()
                .anyRequest().authenticated();

        return http.build();
    }

    @Bean
    public ClientRegistration clientRegistration() {
        ClientRegistration cr =
                ClientRegistration.withRegistrationId("discord")
                        .clientId("123456")
                        .clientSecret("ABCD")
                        .scope(new String[]{"email", "identify"})
                        .authorizationUri("https://discord.com/oauth2/authorize")
                        .tokenUri("https://discord.com/api/oauth2/token")
                        .userInfoUri("https://discord.com/api/v10/users/@me")
                        .userNameAttributeName("username")
                        .clientName("Discord")
                        .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                        .redirectUri("http://localhost:8082/testauth")
                        .build();
        return cr;
    }

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        // maps each ClientRepository to its registrationId
        return new InMemoryClientRegistrationRepository(clientRegistration());
    }
}

However, whenever it redirects it's the Discord authentication page over and over. After some debugging described below I realized that while the grant code is being received from Discord and stored in a session, the request never seems to be sent for access token and OAuth2LoginAuthenticationFilter seems to always exit prematurely.

By setting a breakpoint inside HttpSecurity, I can see that there are two Oauth2 filters present:

enter image description here

enter image description here

When I first go to localhost:8082 (any endpoint, since all endpoints require authentication), the code in Oauth2AuthorizationRequestRedirectFilter is executed. The OAuth2AuthorizationRequest is created and is saved in the session:

enter image description here enter image description here

On first request to localhost:8082 the Oauth2AuthorizationRequestRedirectFilter does:

enter image description here

It goes to AbstractAuthenticationProcessingFilter:

enter image description here

Then it goes back to Oauth2AuthorizationRequestRedirectFilter:

enter image description here

And at this point the request data has been added to the session:

enter image description here

The Discord authentication page appears. I click authorize and in OAuth2AuthorizationRequestRedirectFilter it delegates to the next filter:

enter image description here

in which it ignores everything:

enter image description here

But then the data is apparently being saved by some of the filters:

enter image description here

and it goes back to the first Oauth2 filter:

enter image description here

The Discord authentication page appears in the browser. Then I click Authorize there.

Then the Oauth2AuthorizationRequestRedirectFilter delegates to the next filter using doFilter:

enter image description here

However after that the execution goes back to Oauth2AuthorizationRequestRedirectFilter, and it saves the DefaultSavedRequest in session, it contains the code returned by Discord:

enter image description here

And then the page refreshes and it's Discord authentication window again.

The execution goes to AbstractAuthenticationProcessingFilter, and it always goes to the next filter, it also has the code at this point saved in session:

enter image description here

And the Discord authentication page appears again.

So essentially it looks like it never goes to the second stage of authentication, never asks for the token itself. Meanwhile the code it receives from Discord is valid, and I can get the token using Postman without any issues.


Solution

  • I managed to retrieve the access token by changing redirect URI to

     .redirectUri("http://localhost:8082/login/oauth2/code/discord")
    

    Because in AbstractAuthenticationProcessingFilter's doFilter:

     if (!this.requiresAuthentication(request, response)) {
                chain.doFilter(request, response);
            } else {
                try {
                    Authentication authenticationResult = this.attemptAuthentication(request, response);
    

    So the OAuth2LoginAuthenticationFilter's attemptAuthentication would only be executed if the

     protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
            if (this.requiresAuthenticationRequestMatcher.matches(request)) {
                return true;
            }
    

    matcher returns true, which happens if:

       public boolean matches(HttpServletRequest request) {
            if (this.httpMethod != null && StringUtils.hasText(request.getMethod()) && this.httpMethod != HttpMethod.valueOf(request.getMethod())) {
                return false;
            } else if (this.pattern.equals("/**")) {
                return true;
            } else {
                String url = this.getRequestPath(request);
                return this.matcher.matches(url);
            }
        }
    

    I'm not sure what /** means, but for any URL other than /login/oauth2/code/* false was returned for me.

    Now I wonder how do I change the configuration, so that the grant code would get accepted by any redirect URL?

    Update:

    In order to use any redirect URL, pass the Customizer lambda like this:

     http.oauth2Login((OAuth2LoginConfigurer<HttpSecurity> config) -> {
                config.redirectionEndpoint((OAuth2LoginConfigurer<HttpSecurity>.RedirectionEndpointConfig redirection) -> {
                    redirection.baseUri("/testauth");
                });
            });
    

    Spring is the biggest piece of I've seen tbh