I'm using Spring Boot and Spring Security as my Back End, running on "localhost:8080". I have also React Front End, running on "localhost:9000". The communication between them is happening via HTTP requests. I have set up a CORS config in Spring Security, all requests to my Back End from Front End works fine, GET, POST requests - no errors.
However, the only CORS error occurs when Spring Security is sending a redirect after successful authentication. And error occurs not with any redirection - specifically when Spring redirects to external URL, such as "localhost:9000" (my Front End homepage). When redirecting to local URLs, such as "/" or "/bookmarks", there are no CORS errors, after redirection a GET request is sent to my Back End's port sucessfully.
What I want to accomplish: after successful authentication on my custom login form React page, I want my users to be redirected to the homepage of my React app.
A mock fetch request i'm sending to Spring to login
const login = async () => {
const response = await fetch("http://localhost:8080/login", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
username: "admin",
password: "admin",
}),
});
return response;
};
login()
.then((response) => console.log(response))
.catch((err) => console.log(err));
Request to "localhost:9000" after redirection from "localhost:8080"
-- General --
Request URL: http://localhost:9000/
Request Method: GET
Status Code: 304 Not Modified
Remote Address: :80
-- Response Headers --
Accept-Ranges: bytes
Connection: keep-alive
Date: Tue, 02 May 2023 20:29:24 GMT
ETag: W/"15d-ORaJAU+aH9t5HZ/clgri7Q6Qsaw"
X-Powered-By: Express
-- Request Headers --
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=F37609EF95D9A21AA777DD208F8BF3FF
Host: localhost:9000
If-None-Match: W/"15d-ORaJAU+aH9t5HZ/clgri7Q6Qsaw"
Origin: null
Referer: http://localhost:9000/
sec-ch-ua: "Not_A Brand";v="99", "Google Chrome";v="109", "Chromium";v="109"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
Access to fetch at 'localhost:9000/' (redirected from 'localhost:8080/login') from origin 'localhost:9000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain configuration(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.cors(Customizer.withDefaults())
// Requests authorization
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/login").permitAll()
.anyRequest().authenticated())
// Login page config
.formLogin(form -> form
.loginPage("http://localhost:9000/auth/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("http://localhost:9000", true)
.permitAll())
.httpBasic(Customizer.withDefaults())
.build();
}
}
@Configuration
public class CorsConfig {
@Bean
CorsConfiguration corsConfiguration() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:9000/"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "DELETE"));
configuration.setAllowedHeaders(Arrays.asList("Content-Type", "X-XSRF-TOKEN", "Authorization"));
configuration.setAllowCredentials(true);
return configuration;
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration());
return source;
}
}
Solved this issue for myself a long time ago, so I don't remember all the details and can't provide very precise answer. But I hope this will help.
Changed my approach. It is probably not a good practice to have your back-end to handle redirects on the front end (at least when you have a separate front end like React JS). It is better to separate concerns and follow single-responsibility principle. So instead of using Spring Security to redirect user after login, my app is now sending a login request from front end to back end, and if login is successful - then I am using actual front end to navigate to desired page. Without Spring Security sending a redirection request to front end.
HTTP requests from localhost behave a bit differently than general requests. I think the issue was that some information, like some header (perhaps the Origin header), is missing or incomplete when making requests from localhost, so it was blocked by CORS on the front end. This is not really an issue, but rather how localhost HTTP requests are working. But again, I can't be sure.