CORS Simply Explained

Janne Kemppainen |

If you're doing web development there is one thing that you have quite likely been bitten by, CORS. It stands for Cross-Origin Resource Sharing and simply put it is a mechanism which is used by browsers to determine whether or not a request to another domain can be made.

CORS requests typically happen when you try to access an API located on some other domain with JavaScript on your site. If your website and the API are served on the same domain then there are no cross-origin requests because they all go to the same address.

After reading this article I suggest you take a look at this documentation from Mozilla if you need a more in-depth view of the subject.

Simple requests

Here is a completely imaginary situation. Let's assume that for some reason I have an API server running on a different subdomain that can be used to fetch a list of the most popular posts. Because subdomains are considered as separate origins the API server should properly implement CORS if I ever wanted it to work on my site.

A simple CORS request happens only when a list of conditions is fulfilled such as the method must be one of GET, HEAD or POST, and all of the request headers must be “CORS safe”. For example “Accept”, “Accept-Language” and “Content-Type” with a limited set of allowed values are considered safe.

So what happens when the JavaScript in the browser client tries to access the API?

If the API endpoint path is /popular and it is accessed with the GET method, and the calling code doesn't add any headers that are considered unsafe the browser would then initiate a simple CORS request as shown in the image below.

CORS simple request

When making the request the browser automatically adds a new header called “Origin” which contains the domain that the request is originating from. The API response should then contain a header called “Access-Control-Allow-Origin” with the same value. If this header is missing from the response the browser will reject it and log an error message to the browser console. The response would never get back to the JavaScript code.

The API could also respond with Access-Control-Allow-Origin: * in which case it can be requested from any domain. However, when using this configuration the request must not contain credentials. Use this value if you need your API to be publicly available. In other cases it is better to configure only the domains that you actually need.

How to implement the server side handling of CORS depends on what programming language you're using and what your run environment is. You may need to configure some middleware that handles the requests properly.

Preflighted requests

If your code adds anything that is not considered to be safe to the requests they will be preflighted. This means that prior to sending the actual request the browser issues an OPTIONS request to check that the server responds with values that allow it to continue.

Let's change the example case a little. The GET method stays the same but we will add an “Authorization” header to the request to verify that the user has permission to use the API.

Because the Authorization header is not in the list of safe headers the following message flow takes place:

CORS preflighted request

The browser will first send an OPTIONS request that defines the origin, the HTTP method and a list of the headers it wants to add to the request. The server then responds back with the allowed origin, methods and a list of allowed headers.

If the response to the OPTIONS request satisfies the browser then the actual GET request happens normally. The Authorization token is only included in the second request. Similarly for POST requests the OPTIONS request would not contain any data.

Note that the OPTIONS request does not contain the Authorization header.

How was I bitten by CORS?

Yes, I've also had to do some head scratching with CORS.

I was recently building a small API in Google Cloud Run and had a frontend on another domain which needed to do authorized requests to the backend API.

The frontend obtained an authentication token from Google and passed that in the Authorization header to the backend API but no matter what I tried to do in my application code I just couldn't get it to work.

The problem was that when end-user authentication is enabled in Cloud Run it expects every request to be authenticated. Because the browser doesn't include the Authorization token in the OPTIONS request it failed with status 403 forbidden. And because of that the actual request never took place. This bug is also listed in the Google issue tracker.

So how did I fix that?

I moved the frontend to be served from Firebase and configured rewrite rules to the Cloud Run instance. This way all of my requests were for the same origin and I didn't need to worry about CORS anymore!

So, sometimes solving CORS issues might mean getting rid of CORS altogether..

Subscribe to my newsletter

What's new with PäksTech? Subscribe to receive occasional emails where I will sum up stuff that has happened at the blog and what may be coming next.

powered by TinyLetter | Privacy Policy