Fixing 401s with CORS Preflights and Spring Security

1. Overview

In this short tutorial, we’re going to learn how to solve the error “Response for preflight has invalid HTTP status code 401”, which can occur in applications that support cross-origin communication and use Spring Security.

First, we’ll see what cross-origin requests are and then we’ll fix a problematic example.

2. Cross-Origin Requests

Cross-origin requests, in short, are HTTP requests where the origin and the target of the request are different. This is the case, for instance, when a web application is served from one domain and the browser sends an AJAX request to a server in another domain.

To manage cross-origin requests, the server needs to enable a particular mechanism known as CORS, or Cross-Origin Resource Sharing.

The first step in CORS is an OPTIONS request to determine whether the target of the request supports it. This is called a pre-flight request.

The server can then respond to the pre-flight request with a collection of headers:

  • Access-Control-Allow-Origin: Defines which origins may have access to the resource. A ‘*’ represents any origin
  • Access-Control-Allow-Methods: Indicates the allowed HTTP methods for cross-origin requests
  • Access-Control-Allow-Headers: Indicates the allowed request headers for cross-origin requests
  • Access-Control-Max-Age: Defines the expiration time of the result of the cached preflight request

So, if the pre-flight request doesn’t meet the conditions determined from these response headers, the actual follow-up request will throw errors related to the cross-origin request.

It’s easy to add CORS support to our Spring-powered service, but if configured incorrectly, this pre-flight request will always fail with a 401.

3. Creating a CORS-enabled REST API

To simulate the problem, let’s first create a simple REST API that supports cross-origin requests:

@RestController
@CrossOrigin("http://localhost:4200")
public class ResourceController {

    @GetMapping("/user")
    public String user(Principal principal) {
        return principal.getName();
    }
}

The @CrossOrigin annotation makes sure that our APIs are accessible only from the origin mentioned in its argument.

4. Securing Our REST API

Let’s now secure our REST API with Spring Security:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .httpBasic();
    }
}

In this configuration class, we’ve enforced authorization to all incoming requests. As a result, it will reject all requests without a valid authorization token.

5. Making a Pre-flight Request

Now that we’ve created our REST API, let’s try a pre-flight request using curl:

curl -v -H "Access-Control-Request-Method: GET" -H "Origin: http://localhost:4200" 
  -X OPTIONS http://localhost:8080/user
...
< HTTP/1.1 401
...
< WWW-Authenticate: Basic realm="Realm"
...
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Access-Control-Allow-Origin: http://localhost:4200
< Access-Control-Allow-Methods: POST
< Access-Control-Allow-Credentials: true
< Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
...

From the output of this command, we can see that the request was denied with a 401.

Since this is a curl command, we won’t see the error “Response for preflight has invalid HTTP status code 401” in the output.

But we can reproduce this exact error by creating a front end application that consumes our REST API from a different domain and running it in a browser.

6. The Solution

We haven’t explicitly excluded the preflight requests from authorization in our Spring Security configuration. Remember that Spring Security secures all endpoints by default.

As a result, our API expects an authorization token in the OPTIONS request as well.

Spring provides an out of the box solution to exclude OPTIONS requests from authorization checks:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // ...
        http.cors();
    }
}

The cors() method will add the Spring-provided CorsFilter to the application context which in turn bypasses the authorization checks for OPTIONS requests.

Now we can test our application again and see that it’s working.

7. Conclusion

In this short article, we’ve learned how to fix the error “Response for preflight has invalid HTTP status code 401” which is linked with Spring Security and cross-origin requests.

Note that, with the example, the client and the API should run on different domains or ports to recreate the problem. For instance, we can map the default hostname to the client and the machine IP address to our REST API when running on a local machine.

As always, the example shown in this tutorial can be found over on Github.

Related posts:

HttpClient 4 – Send Custom Cookie
Java Program to Find Shortest Path Between All Vertices Using Floyd-Warshall’s Algorithm
Các nguyên lý thiết kế hướng đối tượng – SOLID
A Guide to Java HashMap
How to Read HTTP Headers in Spring REST Controllers
Dynamic Proxies in Java
Lập trình đa luồng với CompletableFuture trong Java 8
How to Kill a Java Thread
Spring REST with a Zuul Proxy
HashMap trong Java hoạt động như thế nào?
Java Program to Generate a Random UnDirected Graph for a Given Number of Edges
Tránh lỗi ConcurrentModificationException trong Java như thế nào?
Serialize Only Fields that meet a Custom Criteria with Jackson
Java Program to Check Whether Topological Sorting can be Performed in a Graph
Working With Maps Using Streams
Java Program to Use the Bellman-Ford Algorithm to Find the Shortest Path
Constructor Dependency Injection in Spring
Convert char to String in Java
Wiring in Spring: @Autowired, @Resource and @Inject
Java Program to Check the Connectivity of Graph Using DFS
Làm thế nào tạo instance của một class mà không gọi từ khóa new?
Returning Custom Status Codes from Spring Controllers
Java Program to Implement Solovay Strassen Primality Test Algorithm
Java Program to Implement PrinterStateReasons API
Tìm hiểu về xác thực và phân quyền trong ứng dụng
JUnit5 Programmatic Extension Registration with @RegisterExtension
Spring Autowiring of Generic Types
HttpClient Timeout
Upload and Display Excel Files with Spring MVC
Java Map With Case-Insensitive Keys
Java Program to Compute the Area of a Triangle Using Determinants
Lớp Collections trong Java (Collections Utility Class)