What API devs should know about CORS

Published June 6, 2021, by Tommy George

CORS: Simply; Complete.

This document and related tech talks are meant to be “just enough to fully grasp what’s happening”. Aimed primarily at API/backend developers who are sometimes confused about why they are “seeing errors about CORS”.

I'm always looking for feedback, better examples, and corrections – especially with a document as technical as this one.

I hope to provide just enough information to fully grasp the what-and-why of CORS, empowering you to “know what to google”, should this not be enough.

Hope it’s helpful!

Foundational Knowledge

Less foundational, but this is what it does:

Just one example scenario

Suppose I accidentally open a link to https://evil.example.com/. That page runs a script to automatically send a GET request from my browser to FOR EXAMPLE, https://bank.example.com/logged-in-views/my-private-info?format=json

The web browser includes a header on this request: “Origin: https://evil.example.com

Your web browser is extremely “helpful”. Assuming you had already been logged in, it will send along your existing login cookie info in the request. (evil.example.com can’t just read other domain cookies, so it has to make a genuine, in-browser request).

When the browser receives the response from the server, the browser examines the headers on the response before determining if the requester can read it.

If the server did not send along a header called Access-Control-Allow-Origin, it will make a safe assumption that the owners of the server do not want to share data across Origins. This is Same-origin Policy at work! So the browser — despite receiving the data — will not let the requesting document READ or access that response.

If the bank server did send an Access-Control-Allow-Origin header on the response, the browser will compare the values provided in that header to the Origin of the document that made the request.

To paraphrase some docs:

If the resource owners at https://api.example.com wished to restrict access to the resource to requests only from https://www.example.com, (i.e no domain other than https://www.example.com can access the resource in a cross-origin manner) they would send:

Access-Control-Allow-Origin: https://www.example.com

It’s up to the server

So it’s always up to the server to look at an “Origin” header, and determine if it should send back an Access-Control-Allow-Origin header on the response. And if it should, what value does it want to supply?

To allow from a specific domain, this could be as simple as the following:

if request.headers['origin'] == 'https://trusted.example.com':
  response.headers['access-control-allow-origin'] = 'https://trusted.example.com'

“When do I need to be aware of CORS?”

When you want an Origin other than your own to have browser-based access to your resource.

This all comes into play in cases where a website might be https://www.bible.com/ but needs to be able to make API Fetch calls to https://api.bible.com/ on the client side (in the browser).

Bonus content: What’s this about “preflight” requests?

The Fetch spec (that defines CORS) has a category of requests referred to as “simple requests”. Those don’t send an automatic “preflight” request. But, all others do.

To directly quote (emphasis mine) (cite):

… for “preflighted” requests the browser first sends an HTTP request using the OPTIONS method to the resource on the other origin, in order to determine if the actual request is safe to send. Such cross-origin requests are preflighted since they may have implications for user data.

For example: If a preflight check describing a request for DELETE bank.example.com/my-account fails, the browser should not send that DELETE request.

The preflight request will usually contain headers that describe the “real” request that it intends to make:

This preflight request gives the server a chance to confirm or deny that a given request method, set of headers, and provided Origin are allowed or disallowed.

That response to a preflight might include several headers, for example:

What about XSS or CSRF?

CORS configuration allows opting out of certain protections provided by Same-origin Policy. It does not necessarily enhance security at all.

You must still protect against CSRF explicitly, especially in the case of state-changing requests. As well as considering solutions like Content Security Policy to prevent certain XSS and data injection attacks.

There’s a lot more

CORS is part of the Fetch API spec. It’s a way to enable read-access to cross-origin requests, which are blocked by default per the Same-origin policy.

There’s actually a lot more to be said about both. CORS has way more options to define more-or-less strict behavior. Some of which were hinted at with the list of Access-Control-Allow-* headers mentioned above.