Security Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

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.

Security bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

Leave a Reply

avatar
  Subscribe  
Notify of