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:
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:
On first request to localhost:8082
the Oauth2AuthorizationRequestRedirectFilter
does:
It goes to AbstractAuthenticationProcessingFilter
:
Then it goes back to Oauth2AuthorizationRequestRedirectFilter
:
And at this point the request data has been added to the session:
The Discord authentication page appears. I click authorize and in OAuth2AuthorizationRequestRedirectFilter
it delegates to the next filter:
in which it ignores everything:
But then the data is apparently being saved by some of the filters:
and it goes back to the first Oauth2 filter:
The Discord authentication page appears in the browser. Then I click Authorize there.
Then the Oauth2AuthorizationRequestRedirectFilter
delegates to the next filter using doFilter
:
However after that the execution goes back to Oauth2AuthorizationRequestRedirectFilter
, and it saves the DefaultSavedRequest
in session, it contains the code returned by Discord:
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:
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.
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