jameslittle.me

Why do we encounter CORS errors?

August 18, 2019

"CORS" errors are a certain type of web development errors that can be pretty confusing. Cross-Origin Resource Sharing related errors pop up when the web browser is trying to protect you from potential security vulnerabilities. When you don't expect them, though, they can be pretty irritating—the error messages don't make it entirely clear what you need to do to resolve the problem, and CORS itself is a complex enough topic that you won't find any quick resolution steps on the internet.

An image of a CORS error in the Firefox developer tools

Why do CORS errors occur in the first place? You nearly always see them when the following three cases are true: when the browser is

  1. asked to load a remote resource (like a script, font, a page via AJAX, a WebGL texture, but not, notably, an image via the <img> tag) and that resource is
  2. from an external domain, i.e. a domain that isn't the same as the one in your address bar, but
  3. the server doesn't specify (by sending the correct HTTP headers) that the original domain is allowed to use that resource.

When all three of those cases are true, the browser will stop itself from loading the file, and will throw an error like the one above in the Javascript console. This might manifest as a font that doesn't load, an AJAX call that doesn't succeed, or other "why won't this show up" kinds of problems.

Why does the browser stop remote resources from loading? It's a permissions issue: the browser wants to make sure that the external server has given its blessing to the website trying to load its content.

Imagine I want to steal information from a victim website. I set up an evil website that loads a script from the victim site's server. Without CORS protections in place, my evil site can download and run the victim site's script and send the victim site's data back to me. In this way, servers can protect themselves from inappropriate content access.

Unfortunately, this means that if you build a site and an external service from which it loads data, you'll have to set up this external service so that it knows it is allowed to serve data when the site is asking for it.

In the case of the Bowdoin Orient's site, the font CDN Content Distribution Network: another server whose job it is to cache and serve static files very quickly. lives on a different server from the actual site itself. That means the original page (the origin, or https://bowdoinorient.com) is trying to load content from a different domain (font-cdn.bowdoinorient.co). If we don't set up the font CDN server to allow itself to serve content to pages on bowdoinorient.com, the browser will refuse to load that content, meaning (in this case) our fonts will break.

My goal is to create a short guide that explains what's going on when you encounter these weird error messages, and to describe what the browser expects from the files it downloads and why.

HTTP and Headers#

Let's talk about the HyperText Transfer Protocol.

What happens when you go to a website, like https://jameslittle.me? On a high level, your browser sends an HTTP request (which is just a bit of text) to my server, and my server, via a program like Apache that runs continuously and is built to answer web requests, sends back an HTTP response with the contents of my web page.

Every time a web page loads, several of these HTTP requests are sent: the first one is for the HTML document that was requested, and the rest are for any images, scripts, fonts, or stylesheets that the HTML document says is needed. The first one is directly related to the page you asked for in your browser's address bar; any others are automatically sent by the browser as specified by the first document the browser gets back. For each request that gets sent, the server to which it gets sent responds with the data the browser asked for. Those request/response pairs make up the contents of the web page, and control what your browser displays to you.

The HTTP headers for both a request (on the bottom) and a response (on the top).

We can dig into the request/response pairs in greater depth by looking at them in the Web Inspector. I use Firefox, so that's where these inspector screenshots come from. But every reasonable web browser has an inspector these days, and all of them let you look at the contents and headers of an HTTP request and response. Each request and response has two parts: the headers and the payload.

Headers: The headers define configuration and other metadata for the message. They are plain-text key-value pairs that go at the beginning of both HTTP requests and responses. HTTP request headers are messages that the browser wants to tell the server, while HTTP response headers are messages that the server wants to tell the browser.

Payload: The payload for an HTTP response will almost always be the contents of the requested file. HTTP requests can sometimes have a payload, though most of the time this payload is empty — usually, an HTTP request only consists of headers describing the file the browser is asking for.

How HTTP headers relate to CORS errors#

As I described earlier, CORS errors occur when the server hasn't specified that the browser is allowed to load the resource. That permission is communicated using an HTTP header on the response: the server will add a header that says "If any page on bowdoinorient.com downloads a file from me, it is allowed to use it."

Remember: HTTP headers are key-value pairs in the beginning of an HTTP message. The one that describes this permission granting has a key of Access-Control-Allow-Origin, and has a value of the origin allowed to use that file (in this example, bowdoinorient.com). If that key-value pair is present on an HTTP response from an external server (like a font CDN), any time a page on bowdoinorient.com wants to load that font, the browser will allow that to happen.

Therefore, if you're getting console warnings about CORS headers not being properly included, it means you have to change the configuration on your server: your server needs to be including HTTP headers in the response so that the browser knows it's allowed to use the file it downloaded.

What kinds of headers should I include?#

It sort of depends on what your browser is asking for — while the console error messages might not immediately be clear, you can usually tell which header is missing from the error message. In the example above, the server needs to attach the Access-Control-Allow-Origin header with a value that says that bowdoinorient.com pages are allowed to use the font file.

I mentioned above that the header's value can be set as bowdoinorient.com, and that will allow pages on bowdoinorient.com to load the resource. But we could also configure the value to be *, which would specify that any site is allowed to use that resource. General purpose CDNs, like Google Fonts, will have this * as the value for their Access-Control-Allow-Origin header.

CORS errors can manifest in different ways, since there are different permissions that a server can specify. There are more headers that give more granular permissions to browsers. These headers might include:

Access-Control-Allow-Methods: describes which HTTP methods (GET, POST, PUT, DELETE, etc.) are allowed to be used on a given URI. When you use Javascript to make an AJAX request, sometimes it will send a preflight request: an additional request beforehand to see what sorts of requests the browser is allowed to make before it actually makes the request. The server will respond with this header to let the browser (and, ultimately, you) know what kinds of HTTP methods you can use next.

Access-Control-Allow-Headers: describes which request headers are allowed to be sent while asking for a given resource. For example, the browser (again, through Javascript) might specify in a request header that it wants JSON-only responses (content-type: JSON would be the header tacked onto the HTTP request). If you send a server a header it doesn't expect, it might reject the request altogether.

Conclusion#

When struggling with CORS errors, the concepts I always have to remind myself are:

  1. The file is usually being properly downloaded, but the browser is blocking the file from being used
  2. The server needs to be changed to give the browser permission to use that file
  3. That change needs to be a new header that gets included with the HTTP response

Those three concepts are the biggies. And ultimately I always feel like the fix was something simple that just requires a large research journey. Security is just like that, I guess.