More often than not CORS mean little more to developers than just getting rid of the infamous browser error CORS error in order to continue cranking out features as soon as possible. In other words, it’s not the best understood concept out there.

Hence, to shed more light upon the topic, I’d like to give some working examples of CORS setups and show why they work. The more detailed explanation about the backing concepts I relinquish to the excellent MDN documentation if you’d like to dig deeper.

What is it

For security reasons a browser denies frontend JavaScript loaded from origin A (i.e. http://dogpics.com) to embed resources which have to be fetched from a different origin B (i.e. http://catpics.com). That is in essence what is normally refered to as same-origin-policy.

To bypass that restriction (but still prevent the inadvertent usage of resources) origin A and origin B can agree on a deliberate exchange of resources. They do this by defining a Cross-Origin Resource Sharing strategy (or CORS for short). When done properly the scenario described above becomes possible again and frontend JavaScript loaded via http://dogpics.com can also embed resources from http://catpics.com.

When do I need it

Well, whenever your frontend JavaScript tries to fetch resources from different origins according to the same-origin-policy. In other words, it applies if origin B differs in either the protocol, the domain or the port. So for our origin A http://dogpics.com this means:

origin BSame Origin Why
https://dogpics.comnopeprotocol differs
http://dogpics.com/cat.pngyesonly the path is different - the origin is the same
http://api.dogpics.comnopedomain is different (yes, this includes sub-domains)
http://dogpics.com:8443nopeport is different

CORS headers and their effect

Basically, it’s the origin B server which determines who is able to embed its resources by using a bunch of HTTP-headers. Nevertheless, for some use-cases the requesting party (here the frontend JavaScript of origin A) has to be adjusted too. Also depending on the use-case is the choice of CORS-headers to be applied. Here’s a short recap of the most important CORS-headers (again, details can be found here) by which origin B can control who else can fetch its resources:

  • Access-Control-Allow-Origin: can only be set to one specific origin which is then allowed to fetch from this server. It can also be set to wildcard '*' but this value is mutually exclusive to "include" credentials mode (which makes it useless when dealing with auth-cookies).
  • Access-Control-Allow-Credentials: When a request’s credentials mode (Request.credentials) is "include", browsers will only expose the response to frontend JavaScript code if the Access-Control-Allow-Credentials value is true (see here for more details)
  • Access-Control-Allow-Methods: all allowed methods which can be used in the actual fetch call
  • Access-Control-Allow-Headers: Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. Also allows wildcard '*' but again doesn’t work with auth-cookies/header etc.

Examples of CORS requests

Example 1 (without auth):

Situation:

We’d like to fetch public (no authentication is necessary) JSON-content using a GET-request. Since we request Content-Type: application/json this will cause a preflight request before the actual GET-request is issued (find details here about the difference between simple requests and preflight requests).

Request:

const config = { "headers": {"Content-Type": "application/json"} };
fetch(url, config).then(response => response.json());

Header-Setup:

Access-Control-Allow-Origin: '*'
Access-Control-Allow-Headers: 'content-type'
Access-Control-Allow-Methods: 'GET'

Result:

  • Since we don’t have to deal with authentication whatsoever we can use the '*'-value for Access-Control-Allow-Origin (Notice: the fact that it works doesn’t mean that it’s a good idea to allow access for the whole world).
  • Since we want to use HTTP-GET we have to allow that method using Access-Control-Allow-Methods: 'GET'
  • And finally, since we want the server to return a JSON-representation of the resource we have to allow the Content-Type header to be used.

Example 2 (with Cookies):

Situation:

For whatever reason the server of origin B needs its cookies to be included in the request (i.e. session-cookie).

Request:

const config = { 
    "credentials": "include", // makes sure that cookies will be sent 
    "headers": {"Content-Type": "application/json"} 
};
fetch(url, config).then(response => response.json());

Header-Setup:

Access-Control-Allow-Origin: 'http://localhost:8081'
Access-Control-Allow-Credentials: 'true'
Access-Control-Allow-Headers: 'content-type'
Access-Control-Allow-Methods: 'GET'

Result:

The usage of cookies has a bunch of implications for the setup to work:

  • first, the client has to use include credentials mode to make sure the browser includes the origin B cookies when issuing the GET-request.
  • consequently, origin B has to set the CORS-header Access-Control-Allow-Credentials to true - if that is not set (or set to false), it won’t work.
  • when using Access-Control-Allow-Credentials:true the wildcard '*' for Access-Control-Allow-Origin is not applicable anymore - hence we have to be explicit about who actually is origin A.

Example 3 (with Auth-Tokens):

Situation:

When your authentication setup doesn’t use cookies but auth-headers you strangly don’t have to use the credentials: include mode (although you can of course).

Request:

const config = { 
    "headers": {
        "Content-Type": "application/json", 
        "Authorization": "Basic SXQncyBub3Qgd29yayB0aGUgZWZmb3J0IGR1ZGU=" 
    } 
};
fetch(url, config).then(response => response.json());

Header-Setup:

Access-Control-Allow-Origin: '*'
Access-Control-Allow-Credentials: 'false'
Access-Control-Allow-Headers: 'content-type, authorization'
Access-Control-Allow-Methods: 'GET'

Result:

This is basically the same setup as in Example 1 except that we explicitly allow the Authorization header as well. Notice: In those cases (with or without credentials-mode) make sure that you use a TLS-connection.

Test it out yourself

When you’d like to test this yourself head over to this repository in order to setup a simple 2-server constellation where one origin (http://localhost:8081) is embedding a resource of the other one (http://localhost:8080). Using the UI you can tweak the server-headers and the config for the fetch-request.

CORS UI

In the server-logs you can inspect what headers and cookies are transmitted by the CORS-request (and how OPTIONS- a.k.a. preflight- and GET-request differ).