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:

Creating a Custom Starter with Spring Boot
Tạo ứng dụng Java RESTful Client với thư viện Retrofit
Java Program to Implement Quick Sort Using Randomization
Exploring the Spring 5 WebFlux URL Matching
Java Program to Use Above Below Primitive to Test Whether Two Lines Intersect
Giới thiệu java.io.tmpdir
Java Program to find the peak element of an array using Binary Search approach
Custom HTTP Header with the HttpClient
Introduction to Spring Cloud OpenFeign
Giới thiệu Java Service Provider Interface (SPI) – Tạo các ứng dụng Java dễ mở rộng
Introduction to Spring Cloud Stream
Java Program to Implement a Binary Search Tree using Linked Lists
Extra Login Fields with Spring Security
Tìm hiểu cơ chế Lazy Evaluation của Stream trong Java 8
Mệnh đề Switch-case trong java
Jackson – Unmarshall to Collection/Array
Java Program to Generate a Sequence of N Characters for a Given Specific Case
Kiểu dữ liệu Ngày Giờ (Date Time) trong java
Java Program for Douglas-Peucker Algorithm Implementation
Configuring a DataSource Programmatically in Spring Boot
Java Program to Find the Mode in a Data Set
Java Program to Implement Sparse Array
Java Program to Implement Sieve Of Sundaram
Java Program to Implement Gale Shapley Algorithm
Semaphore trong Java
Posting with HttpClient
Getting Started with Custom Deserialization in Jackson
Introduction to Liquibase Rollback
Spring 5 Testing with @EnabledIf Annotation
Partition a List in Java
Apache Commons Collections OrderedMap
Java 8 Stream API Analogies in Kotlin