Understand CORS once and for all

CORS stands for Cross Origin Resource Sharing

It is a security implementation in web browsers that protects access to resources.


Same Origin

By default, we can only access our own origin.

If our domain is foo.com our origin is foo.com: we are allowed to access our origin since we are talking about the same origin.


However, when we try to access bar.com from our origin foo.com this fails with:

Access to fetch at 'https://bar.com/' from origin 'https://foo.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Because we are trying to talk to another origin. This is when we talk about cross-origin. And that’s why CORS comes into play here.

Cross Origin

We can only access bar.com from foo.com, if bar.com says it’s okay that we can access it.

The way the server can let us pass is by putting an Access-Control-Allow-Origin header into its HTTP responses.


Access-Control-Allow-Origin

As a value, we can pass a domain we want to grant access:

Access-Control-Allow-Origin: https://foo.com

Or to allow access to anybody out there:

Access-Control-Allow-Origin: *

If we want to allow access to a defined list of origins but not to all origins, there is no way to do this with the Access-Control-Allow-Origin header directly.

Comma, space or semicolon separated lists of domains are not valid.

Instead, we have to implement the check on the server and send a corresponding response back.

const incomingOrigin = "https://foo.com"
const allowedOrigins = ["https://bar.com", "https://baz.com"]

if (allowedOrigins.includes(incomingOrigin)) {
	headers["Access-Control-Allow-Origin"] = incomingOrigin
}

Setting the Access-Control-Allow-Origin header on the server fixes the CORS issue, and we can successfully fetch data from the whitelisted origin.

However, this only works for simple requests.

Simple requests are requests that can only contain one of the allowed HTTP methods and only whitelisted headers, as well as a few other requirements that have to be met.

When we make more exotic calls, CORS won’t allow us to talk to the server directly, instead preflight requests are made to ask the server if what we are asking for looks okay to the server.


Preflight communication

Preflight request

The web browser that’s implementing the CORS standard is making the preflight requests for us. We don’t have to handle those.

What’s happening under the hood is that the browser sends an HTTP OPTIONS call asking for methods and headers we are trying to use in our real call.

OPTIONS /resource HTTP/1.1
Host: bar.com
Origin: https://foo.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Api-Key
...

With this OPTIONS request, the browser asks bar.com if it is okay if we - foo.com - can make a DELETE request and include an Api-Key header with our request.


Preflight Response

We respond with a 204 status code from the server.

The status message for this code is “No Content”.

Since all of this preflight communication is happening before we are actually able to talk with our server we want to keep our response as small as possible.

This is why we don’t include any extra body payload.

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Methods: DELETE, POST, GET, OPTIONS
Access-Control-Allow-Headers: Api-Key
...

With this response, our server tells the browser it’s safe and okay to send a request from our origin with our method and headers.

The server does so by including us as a valid origin and by whitelisting the allowed request methods and headers.


Main communication

When preflight is successful, the browser continues with our actual request:

DELETE /resource HTTP/1.1
Host: bar.com
Origin: https://foo.com
Api-Key: super-secure-123
...

And we finally get a response from our server:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://foo.com
...

Outside browser world

CORS is only implemented by browsers as an extra layer of security to help protect against cross-site-scripting, etc.

The whole thing only consists of HTTP headers and the browser checking those.

This means making HTTP requests with curl or wget or fetch inside Node and not in the browser, this will work. There is no one preventing us from making those requests since CORS is not implemented here.


background scene
Join the garden
Finally enjoy quality-first content.
'cause low-quality sucks

legal privacy