The essence of the problem

A typical case for web applications:

  • frontend part is hosted on one server
  • backend is represented with a monolith or several microservices (or their gateway) on another server (or servers)

In this case, whenever user interacts with this frontend to make a call to any microservice, browser is supposed to make a call to a server which domain name differs from the domain name of the frontend server, just like on the picture:

CORS request

It is so called cross-origin request.

What is CORS

CORS (stands for Cross-Origin Resource Sharing) - this is an approach for a browser to find out whether web-application with one origin is allowed to get access to specific resources within different origin. It is based on additional headers that are sent by browser to different origin.

In most of nowadays browsers there is enabled CORS policy which checks such kind of calls: if browser detects cross-origin calls it decides whether it has to be prechecked:

  • method of the request can cause side-effects on server (usually POST, PUT, DELETE)
  • contains some custom headers

If request has to be prechecked, browser sends preflight OPTIONS request. This request contains all necessary info like: request type, origin, headers. Responsibility of the server is to check what are allowed values for that fields and send back proper response with headers: Access-Control-Allow-Origin, Access-Control-Request-Method, Access-Control-Request-Headers.

This response is used by the browser to verify whether it is allowed to make actual request to the server - it checks whether current origin is among values listed in Access-Control-Allow-Origin header and the same for methods and headers.

If our server is not properly configured and it does not return allowed values or browser fails CORS validation, we will get an “Origin has been blocked by CORS policy” with “no ‘access-control-allow-origin’ header is present” exception and it will look something like this for GET and POST requests:

Origin has been blocked by CORS policy responses

Do we need to handle CORS?

The usual answer to the question “How to deal with CORS error in browser?” I found was to allow all origins to make calls. But at the same time, these CORS restrictions are needed to protect our users, so we have to find the way to deal with this CORS without relieving protection. To do this we have to configure our API layer to handle preflight requests properly.

NOTE: Sometimes for development purposes you might want to disable CORS, but please be sure that eventually it will be handled correctly.

The way to allow only specific origin in Spring boot application

Usually, the only thing you have to do in case of simple spring boot application is to add @CrossOrigin annotation to specific controller or method of the controller (you can have really granular control over requests).

So it will look something like this:

 @RestController
 @CrossOrigin("https://www.javarubberduck.com")
 @RequestMapping("/ducks")
 public class DuckController {
 
     @PostMapping(
         consumes = MediaType.APPLICATION_JSON_VALUE,
         produces = MediaType.APPLICATION_JSON_VALUE
     )
     @ResponseStatus(HttpStatus.CREATED)
     public Duck addSslCertificate(@RequestBody Duck duck) {
         // business logic here
     }
 }
 

NOTE: @CrossOrigin annotation without value inside has * as a default value.

Spring cloud gateway configuration of CORS

Common approach is to use gateway server which will handle requests to specific servers. Let’s see how we can configure our gateway in this case.

By default any cross-origin request is denied (for safety reasons). So we have to explicitly configure our server like this:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "https://www.javarubberduck.com"
            allowedHeaders:
              - content-type
            allowedMethods:
              - GET
              - POST

As you can see we have to configure each crucial part: allowed origins, allowed headers, allowed methods.

This configuration means that whenever gateway receives OPTIONS request for any path it will return similar response:

  HTTP/1.1 200
  status: 200
  Vary: Origin
  Vary: Access-Control-Request-Method
  Vary: Access-Control-Request-Headers
  Access-Control-Allow-Origin: http://www.javarubberduck.com
  Access-Control-Allow-Methods: GET,POST
  content-length: 0

This response allows browser to send subsequent call.

Conclusion

CORS policy is meant to improve security for us and our users and we have to properly configure servers in order to handle it correctly. It is an additional layer of protection and it is never enough when we talk about these things. Moreover, we can see it is pretty easy to configure spring cloud gateway to handle cross-origin requests without the overhead or additional code written so there are no arguments not to do it.

Updated: