spring-security icon indicating copy to clipboard operation
spring-security copied to clipboard

Simplify Configuring Log In using Twitter / X v2 APIs

Open rwinch opened this issue 1 year ago • 1 comments

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

rwinch avatar Jan 08 '25 19:01 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.

  1. 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.

  2. 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.

  3. 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.

rwinch avatar Jan 09 '25 17:01 rwinch