Aman Explains

Settings

Demystifying Cross-Origin Resource Sharing (CORS) on Web

October 06, 2023• ☕️ 4 min read

Imagine you’re working on the front-end of a web application, integrating a REST API endpoint provided by your back-end colleague. You swiftly craft an AJAX call using a few lines of JavaScript with the fetch API. However, to your surprise, your browser console lights up with perplexing CORS errors. As you investigate further in the browser’s network tab, you encounter an additional HTTP request with the OPTIONS method, an unfamiliar sight that adds to your frustration. What was expected to be a quick 5-minute task has led you down a rabbit hole, unraveling the complexities of the CORS protocol.

In the past, I faced a similar challenge and resorted to a temporary fix by installing a CORS extension in Chrome. Looking back, I wish I had taken the time to fully comprehend this concept.

This article aims to guide you through the intricacies of CORS, providing clarity and demystifying its purpose. Let’s dive in and unravel the mystery of CORS.


What is Cross-Origin Resource Sharing (CORS)

As per MDN documentation, the Cross-Origin Resource Sharing (CORS) is a HTTP-header based mechanism by which a server indicates to the browser of any origins other than its own origin from which it should permit loading resources.

To understand this definition, we need to understand the meaning of origin. An origin is a combination of scheme, host, and port. For a given URL, https://store.company.com:443, the breakdown would be:

  • Scheme: https
  • Host: store.company.com
  • Port: 443

origin is combination of scheme, host name, and port

For two origins to be the same, these three values should match. The following table gives examples of origin comparisons with the URL http://store.company.com/dir/page.html:

Same-Origin vs Cross-Origin
URLOrigin TypeReason
http://store.company.com/dir2/other.htmlSame OriginOnly the path differs
http://store.company.com/dir/inner/another.htmlSame OriginOnly the path differs
https://store.company.com/page.htmlCross-OriginDifferent protocol
http://store.company.com:81/dir/page.htmlCross-OriginDifferent port (http:// is port 80 by default)
http://news.company.com/dir/page.htmlCross- OriginDifferent host

With this new knowledge of Origin, let’s look at a few practical examples and understand what requests use CORS.

When you load a URL https://domain-a.com in your browser, the browser will parse the initial web document and make requests for other resources like JS, CSS, and images. All these requests to https://domain-a.com/* are known as Same-origin requests and are always allowed by your browser. But if any resources were coming from another origin, for example, https://domain-b.com, these requests are known as Cross-origin requests and are controlled by Cross-Origin Resource Sharing (CORS) mechanism.

Cross-origin requests are controlled by the CORS protocol

The following diagram will make it more clear. Note that the main request to the initial document (e.g., index.html) defines the origin.

Cross-origin requests are controlled by Cross-Origin Request Sharing protocol

Another example of a CORS request would be if JavaScript code (browser script) served from https://domain-a.com initiates a Fetch API request for https://domain-b.com/api/products.

For security reasons, browser restricts cross-origin HTTP requests initiated from scripts—the front-end JS code running in your browser. It means if you are making a REST API call using FETCH or XMLHttpRequest to a different origin, you might get some CORS errors in your console unless the response from the other origin server includes the right CORS headers. This is due to the fact that both FETCH and XMLHttpRequest follow the same-origin-policy security mechanism.

These requirements of user-agents (browsers) are part of fetch algorithm. From a server developer perspective, you need to specify a set of headers indicating whether a response can be shared cross-origin or not.

Let’s dig a little deeper into this CORS protocol and understand what steps the browser takes to mitigate risk associated with unsafe cross-origin requests.


Simple and Preflight requests

While accessing a resource on another origin, a request is considered Simple when it fulfills all of the following requirements:

This list of requirements has been taken straight from MDN Simple Request documentation.

Whenever a request doesn’t meet the criteria of a simple request, it’s considered a CORS-preflight request. As part of user-agent requirements mentioned in the fetch algorithm, the browser will automatically craft these preflight requests for your front-end JS code. Front-end Developers don’t need to make these requests explicitly.

For example, if your front-end JavaScript code makes a fetch request to another origin, the following steps occur:

  1. Based on the request parameters that your JS code is trying to make, if it’s not a simple request, the browser will mark it as a to be preflight request.
  2. The browser will then automatically generate an OPTION request and can include the following headers:
  3. The server, upon receiving this CORS-preflight request, can respond with the following headers:
    • Access-Control-Allow-Methods: indicates the list of permitted HTTP methods to be used with actual request.
    • Access-Control-Allow-Headers: indicates the list of permitted HTTP headers to be used with actual request.
    • Access-Control-Allow-Origin: indicates the origin that is allowed to access this requested resource. The value can be a * (wild card), which means that the resource can be accessed by any origin.
    • Access-Control-Max-Age: indicated the number of seconds (5 by default) the response of the preflight request can be cached.

To understand it better let’s consider an example of a script running in your browser making a Fetch API call. This fetch request is being generated from origin https://domain-a.com to another origin https://domain-b.com.

fetch("https://domain-b.com/api/todos", {
  method: "POST",
  body: JSON.stringify({
    name: "learn about CORS",
    isCompleted: false,
  }),
  headers: {
    "Content-Type": "application/json",
    "X-Aman-Explains": "awesome",
  },
});

This example script is sending an XML body with a POST request. Since the request uses Content-Type of application/json along with a non-standard HTTP X-Aman-Explains request header, it no longer meets the criteria of a Simple request. The browser will initiate a preflight request with OPTION method, including two additional headers as discussed above.

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Aman-Explains, Content-Type

Upon receiving this preflight request, the server will respond with the following headers:

Access-Control-Allow-Origin: https://domain-a.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-Aman-Explains, Content-Type
Access-Control-Max-Age: 3600

By sending Access-Control-Allow-Origin: https://domain-a.com, the server is stating that this origin is allowed to access this resource in the actual request. Additionally, it indicates that POST, and GET are valid methods to query the resource without any concerns. Moreover, as shown in the comma-separated list of allowed headers, it confirms that X-Aman-Explains and Content-Type are valid HTTP headers and are allowed to be used in the actual request as well.

Lastly, the server is happy for the browser to cache this preflight response for 1 hour (3600 seconds), and thus no need to make another preflight request within this period.

Note: Each browser has a maximum internal value that takes precedence when the Access-Control-Max-Age exceeds it.

Preflight request is made when there are custom headers attached

Once the browser receives this preflight response, it can introspect the CORS HTTP response headers and check that these custom headers (Content-type of application/json and X-Aman-Explains) are allowed by the https://domain-b.com origin. Based on this information, the browser then sends an actual request that results in a JSON response from the server.


Summary

CORS is an HTTP-header based mechanism that allows the origin that initially loads your main document to access resources from another origin.

When executing a request that is not a simple request, the browser sends out a preflight request with OPTION method, along with additional headers, asking the other origin if it’s safe to make the actual future request or not. If it’s approved, the browser will go ahead and fire the actual request. Otherwise, the request will result in CORS failure and an error will be displayed in your browser console.

Resources


Amandeep Singh

Written by Amandeep Singh. Developer @  Avarni  Sydney. Tech enthusiast and a pragmatic programmer.