I’m working on an Angular app where users can pick their OAuth2 provider for login. Here’s my frontend code:
selectAuthProvider(providerName: string) {
const authEndpoint = `http://localhost:8080/auth/oauth2/${providerName}`;
window.location.href = authEndpoint;
}
When the user clicks the button, they get redirected to http://localhost:8080/auth/callback/twitch. I can get the authorization code, exchange it for an access token, and fetch user data successfully.
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth/callback")
public class AuthCallbackController {
@Value("${oauth.twitch.app-id}")
private String appId;
@Value("${oauth.twitch.app-secret}")
private String appSecret;
private HttpHeaders buildAuthHeaders(String token) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + token);
headers.set("Client-Id", appId);
return headers;
}
public ResponseEntity<String> fetchUserData(String token) {
RestTemplate client = new RestTemplate();
HttpHeaders headers = buildAuthHeaders(token);
HttpEntity<String> request = new HttpEntity<>(headers);
String apiUrl = "https://api.twitch.tv/helix/users";
ResponseEntity<String> result = client.exchange(apiUrl, HttpMethod.GET, request, String.class);
return result;
}
public String exchangeCodeForToken(String code) {
RestTemplate client = new RestTemplate();
Map<String, String> payload = new HashMap<>();
payload.put("client_id", appId);
payload.put("client_secret", appSecret);
payload.put("code", code);
payload.put("grant_type", "authorization_code");
payload.put("redirect_uri", "http://localhost:8080/auth/callback/twitch");
String tokenEndpoint = "https://id.twitch.tv/oauth2/token";
Map<String, Object> result = client.postForObject(tokenEndpoint, payload, Map.class);
return result != null ? (String) result.get("access_token") : null;
}
@GetMapping("/twitch")
public ResponseEntity<?> processTwitchCallback(@RequestParam("code") String code) {
String token = exchangeCodeForToken(code);
ResponseEntity<String> userData = fetchUserData(token);
return ResponseEntity.ok().body("");
}
}
Here are my app settings:
oauth.twitch.app-id=hidden
oauth.twitch.app-secret=hidden
spring.security.oauth2.client.registration.twitch.redirect-uri=http://localhost:8080/auth/callback/twitch
spring.security.oauth2.client.registration.twitch.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.twitch.scope=user:read:email
spring.security.oauth2.client.provider.twitch.authorization-uri=https://id.twitch.tv/oauth2/authorize
spring.security.oauth2.client.provider.twitch.token-uri=https://id.twitch.tv/oauth2/token
spring.security.oauth2.client.provider.twitch.user-info-uri=https://api.twitch.tv/helix/users
spring.security.oauth2.client.provider.twitch.user-name-attribute=login
Everything works fine until the redirect part. My security config looks like this:
@Configuration
public class AuthSecurityConfig {
@Bean
public SecurityFilterChain configureAuth(HttpSecurity http) throws Exception {
http
.oauth2Login()
.loginPage("/oauth2/authorization/google")
.defaultSuccessUrl("http://localhost:4200/auth-success")
.failureUrl("http://localhost:4200/auth-failure")
.and()
.oauth2Login()
.loginPage("/oauth2/authorization/twitch")
.defaultSuccessUrl("http://localhost:4200/auth-success")
.failureUrl("http://localhost:4200/auth-failure");
return http.build();
}
}
Google OAuth works perfectly and redirects as expected. I think it’s because Google is a built-in provider in Spring Boot, so I don’t need to manually handle tokens.
Why isn’t defaultSuccessUrl("http://localhost:4200/auth-success") working for Twitch? Could it be that my empty response body is preventing the redirect from triggering?