Search code examples
springhttp-redirectspring-securitycors

CORS error with Spring Security and external URL in defaultSuccessUrl()


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.

JS request

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));

HTTP request/response

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

Error in the console

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.

My Spring Security config

@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();
        }
}

CORS config

@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;
    }
}

Approaches tried and Assumptions made

  • In CORS config tried setting allowed headers, methods and origins to "*". It didn't help.
  • Tried sending a direct GET request from my React app to "localhost:9000". There was no error and I got a response.
  • Perhaps the problem lies in the HTTP response header "Origin", which is set to "null", when getting a response from my Spring app after authentication. I am sending a GET request to "localhost:9000" with "Origin: null" header - maybe because of that the browser doesn't recognize that the request is made from the same origin, so it throws the CORS error.
  • When setting "defaultSuccessUrl()" to a local URL, such as "/", which makes redirection to my Spring app, there is no error, everything works fine. Perhaps default behavior of SuccessAuthenticationHandler was not intended to redirect to external links and overriding and customizing some methods are required.
  • I am using React Router on my Front End, but I doubt that the problem lies within it.
  • I configured all of my Spring endpoints to be secured. When attempting to get access (from browser) to Spring endpoints, I'm succesfully getting redirected to my login form page: "localhost:9000/auth/login". No errors at all.

Solution

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

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

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