Issues with CORS preflight Spring/React, reading from authorized/admin endpoint

Tags: , , ,



Im new to both Java (spring) and React and the most issues I seem to get is by the annoying CORS. I did have same CORS issues before and by looking online I found serveral solutions, but mainly the answers were directing to the back-end, that’s where you need to allow certain domains.

So I added first @CrossOrigin (I know the second one is redundant, still wanted to try everything)

@CrossOrigin(origins = "*", allowedHeaders = "*")
@RestController
@RequestMapping(value = "/api/v1")
public class OrderController {

@Autowired
private OrderRepository orderRepository;

@CrossOrigin
@GetMapping(value = "/admin/orders")
public ResponseEntity<Object> getOrders() {
    List<Order> orders = orderRepository.findAll();
    return new ResponseEntity<>(orders, HttpStatus.OK);
}
......rest code

that did the trick on other controllers, but not this one. For the “Admin” part of “OrderController” as stated above I did a check with Postman and JWT token to see if this endpoint gets blocked. It doesn’t get blocked, I receive the order I requested.

On the front-end I tried serveral options:

.... rest code
useEffect(() => {
        async function getOrders() {

            setError(false);
            toggleLoading(true);

            let url = `http://localhost:8080/api/v1`;

            if (isAdmin) {
                url = `${url}/admin/orders/`;
            } else {
                //fix!!!
                let user_id = 1;
                url = `${url}/orders/customer/${user_id}/`;
            }

            console.log(url);

                try {
                    const result = await axios.get(url, {
                        headers : {
                            "Authorization" : `Bearer ${token}`,
                            'Content-Type': 'application/json',
                            "Access-Control-Allow-Origin": "*",
                        }
                    });
                console.log(result);
                if (result.data.length > 0) {
                    setOrderItems(result.data);
                    console.log(orderItems);
                    toggleLoading(false);
                }
            } catch (e) {
                console.error(e);
                setError("Error retrieving order");
                toggleLoading(false);
            }
        }
..... rest code

Basically there are 3 user roles, customer and admin are used here, both witj JWT authentication. So, I want to sent the headers with the get request, as it needs authentication. On every try (also with response => .then/.catch) I get the ‘Error retrieving order’ part and in my log I find:

CORS Preflight did not succeed

Via method OPTIONS. On the Headers I find:

HTTP/1.1 403 
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,HEAD,POST
Access-Control-Allow-Headers: access-control-allow-origin, authorization
Access-Control-Max-Age: 1800
Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
Content-Length: 0
Date: Sat, 10 Apr 2021 16:33:16 GMT
Keep-Alive: timeout=60
Connection: keep-alive

Then below the first error is a second error with:

NS_ERROR_DOM_BAD_URI

on the GET method, which has the headers:

GET /api/v1/admin/orders/ undefined
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0
Accept: application/json, text/plain, */*
Accept-Language: nl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Authorization: Bearer *key removed*
Access-Control-Allow-Origin: *
Origin: http://localhost:3000
Connection: keep-alive
Referer: http://localhost:3000/

I tried another shot at creating a proxy in the package.JSON to ‘dodge’ the browser CORS

 "proxy": "http://localhost:8080"

Does nothing.

My SpringSecurity config:

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public CustomUserDetailsService customUserDetailsService;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserDetailsService);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //JWT token authentication
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/api/v1/customers/**").hasRole("CUSTOMER")
                .antMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .antMatchers("/api/v1/users/**").hasRole("USER")
                .antMatchers("/api/v1/manufacturer/**").hasRole("MANUFACTURER")
                .antMatchers("/api/v1/authenticated").authenticated()
                .antMatchers("/api/v1/authenticate").permitAll()
                .anyRequest().permitAll()
                .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

So, I have no clue wether it’s bad headers sent from the front-end or something blocking (CORS or Auth) from the back-end. Any advice?

@EDIT:

I forgot to mention, I even added

@SpringBootApplication
@RestController
public class BeershopApplication {

    public static void main(String[] args) {
        SpringApplication.run(BeershopApplication.class, args);
    }

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }
}

In the application.java file.

Answer

Browsers will not allow you to call some domain from another domain, for example from you are calling localhost:8080 from localhost:3000. It is not ok to disable this validation on production but it’s ok if you disable it in the development environment because the production environment will hold both things behind one domain. You can solve this problem in different ways.

  • If you are using webpack, you can add a proxy in webpack configurations on react side like "proxy": "http://localhost:8080" and you can use relative paths in you axios requests like await axios.get('/api/v1/admin', {..., this proxy will forward your requests to localhost:8080
  • You can disable cors on the backend side or add localhost as an allowed origin for some routes for:

@Bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/v1/admin").allowedOrigins("http://localhost:3000");
        }
    };
}

  • You can create a filter where you can add appropriate headers in case of options requests

public class CorsFilter extends OncePerRequestFilter {
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, xsrf-token");
    response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
    if ("OPTIONS".equals(request.getMethod())) {
        response.setStatus(HttpServletResponse.SC_OK);
    } else { 
        filterChain.doFilter(request, response);
    }
  }
}

Add filter:

 .addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class)

  • You can add this configuration:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
            .cors()
        .and()

        ...
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
        configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}

  • You can enable cross-origin on some methods with @CrossOrigin(origins = "http://localhost:3000")


Source: stackoverflow