It is not as simple as I would expect to authenticate using Twitter / X v2 Log In APIs. We should simplify this process. This is inspired by the question at https://devcommunity.x.com/t/oauth2-in-spring-boot-3-implementation/233316

  • [ ] #16377
  • [x] #16379
  • [x] #16380
  • [x] #16382
  • [x] #16383
  • [ ] #16390
  • [ ] #16391

Comment From: rwinch

NOTE: I added this here instead of https://devcommunity.x.com/t/oauth2-in-spring-boot-3-implementation/233316 because X was rejecting my POST due to the links in it. I spent too long trying to figure out what was wrong, so I'm posting it here instead.

Let's go over the changes:

1) X uses PKCE but also requires the client id / secret to be provided as basic authentication. This means that client-authentication-method=none is incorrect. You can change the value to client_secret_basic or remove the property since this is the default. Unfortunately, there is not currently a declarative way to use PKCE + Basic authentication in Spring Security. Instead, you will need to add some code:

@Bean
SecurityFilterChain springSecurity(HttpSecurity http, OAuth2AuthorizationRequestResolver resolver) throws Exception {
    http
        .authorizeHttpRequests( r -> r
            .anyRequest().authenticated()
        )
        .oauth2Login(l -> l
            .authorizationEndpoint(a -> a
                .authorizationRequestResolver(resolver)
            )
        );
    return http.build();
}

@Bean
OAuth2AuthorizationRequestResolver pkceResolver(ClientRegistrationRepository clientRegistrationRepository) {
    DefaultOAuth2AuthorizationRequestResolver resolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,"/oauth2/authorization");
    resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce());
    return resolver;
}

This should be improved when gh-16382 is resolved.

2) In the sample you provided, it has the scope of users.read, but X's /users/me endpoint requires both the users.read and tweet.read scopes.

3) The redirect-uri is required and the value provided in the sample is correct. However, it can be improved to work with any registration id by changing the value to {baseUrl}/login/oauth2/code/{registrationId}. This should be defaulted so the property can be omitted and will be fixed in gh-16377. 4) The user-name-attribute=name is incorrect. Twitter data.username to represent the username. This can be fixed by using the following:

@Bean
DefaultOAuth2UserService userService() {
    DefaultOAuth2UserService service = new DefaultOAuth2UserService();
    // unwrap the response from the user to be the contents of the `data` JSON propert
    service.setAttributesConverter((
            request) -> (attributes) -> (java.util.Map<String, Object>) attributes.get("data"));
    return service;
}
# Since the code above unwrapped the attributes, we can now access the username from the username attribute
spring.security.oauth2.client.provider.x.user-name-attribute=username

We plan to improve this with gh-16390.