What is Cross-Origin Resource Sharing (CORS)?

Simple and Preflight Requests, Handling CORS Error

Cross-Origin Resource Sharing (CORS) is a protocol that enables scripts running on a browser client to interact with resources from a different origin. This is useful because thanks to the same-origin policy followed by XMLHttpRequest and fetch, JavaScript can only make calls to URLs that live on the same origin as the location where the script is running.

For example, if a JavaScript app wishes to make an AJAX call to an API running on a different port, it would be blocked from doing so thanks to the same-origin policy. CORS error in the console will look like this:

CORS — Why Is It Needed?

Most of the time, a script running in the user’s browser would only ever need to access resources on the same origin (think about API calls to the same backend that served the JavaScript code in the first place). So the fact that JavaScript can’t normally access resources on other origins is a good thing for security.

The same-origin policy fights one of the most common cyber-attacks out there: Cross-Site Request Forgery CSRF). In this maneuver, a malicious website attempts to take advantage of the browser’s cookie storage system.

In this context, “other origins” means the URL being accessed differs from the location that the JavaScript is running from, by having:

  • a different scheme (HTTP or HTTPS)
  • a different domain
  • a different subdomain
  • a different port

For example:
Long back, sharing resources from https://ayushv.medium.com with a different domain (google.com/api/getdata), different subdomain
(api.ayushv.medium.com), different port (ayushv.medium.com:5050), different protocols (http://ayushv.medium.com) were not allowed. After CORS became a standard, browsers do allow all these.

However, there are legitimate scenarios where cross-origin access is desirable or even necessary. For example, if you’re running a React SPA that makes calls to an API backend running on a different domain. Web fonts also rely on CORS to work. CORS introduces a standard mechanism that can be used by all browsers for implementing cross-domain requests. The spec defines a set of headers that allow the browser and server to communicate about which requests are (and are not) allowed. CORS continues the spirit of the open web by bringing API access to all.

Identifying a CORS Response

When a server has been configured correctly to allow cross-origin resource sharing, some special headers will be included. Their presence can be used to determine that a request supports CORS. Web browsers can use these headers to determine whether or not an XMLHttpRequest call should continue or fail.

There are a few headers that can be set, but the primary one that determines who can access a resource is Access-Control-Allow-Origin. This header specifies which origins can access the resource. For example, to allow access from any origin, you can set this header as follows:

Access-Control-Allow-Origin: *

Or it can be narrowed down to a specific origin:

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

Understanding CORS Request Types

There are two types of CORS requests: “simple” requests, and “preflight” requests, and it’s the browser that determines which is used. As the developer, you don’t normally need to care about this when you are constructing requests to be sent to a server. However, you may see the different types of requests appear in your network log and, since it may have a performance impact on your application, it may benefit you to know why and when these requests are sent.

Let’s have a look at what that means in more detail in the next couple of sections.

Simple requests (GET, POST, and HEAD)

The browser deems the request to be a “simple” request when the request itself meets a certain set of requirements:

  • One of these methods is used: GET, POST, or HEAD
  • A CORS safe-listed header is used ( Accept, Accept-Language and Content-Language)
  • When using the Content-Type header, only the following values are allowed: application/x-www-form-urlencoded, multipart/form-data, or text/plain
  • No event listeners are registered on any XMLHttpRequestUpload object
  • No ReadableStream object is used in the request

The request is allowed to continue as normal if it meets these criteria and the Access-Control-Allow-Origin header is checked when the response is returned.

Preflight requests (OPTIONS)

If a request does not meet the criteria for a simple request, the browser will instead make an automatic preflight request using the OPTIONS method. This preflight request is made by some browsers as a safety measure to ensure that the request being done is trusted by the server. Meaning the server understands that the method, origin, and headers being sent on the request are safe to act upon.
This call is used to determine the exact CORS capabilities of the server, which is in turn used to determine whether or not the intended CORS protocol is understood. If the result of the OPTIONS call dictates that the request cannot be made, the actual request to the server will not be executed.

The preflight request sets the mode as OPTIONS and sets a couple of headers to describe the actual request that is to follow:

  • Access-Control-Request-Method: The intended method of the request (e.g., GET or POST)
  • Access-Control-Request-Headers: An indication of the custom headers that will be sent with the request
  • Origin: The usual origin header that contains the script's current origin

An example of such a request might look like this:

# Request
curl -i -X OPTIONS localhost:3001/api/ping \
-H 'Access-Control-Request-Method: GET' \
-H 'Access-Control-Request-Headers: Content-Type, Accept' \
-H 'Origin: http://localhost:3000'

This request basically says “I would like to make a GET request with the Content-Type and Accept headers from http://localhost:3000 - is that possible?".

The server will include some Access-Control-* headers within the response to indicate whether the request that follows will be allowed or not. These include:

  • Access-Control-Allow-Origin: The origin that is allowed to make the request, or * if a request can be made from any origin
  • Access-Control-Allow-Methods: A comma-separated list of HTTP methods that are allowed
  • Access-Control-Allow-Headers: A comma-separated list of the custom headers that are allowed to be sent. If you use custom headers (eg. x-authentication-token you need to return it in this ACA header response to OPTIONS call otherwise, the request will be blocked.
  • Access-Control-Expose-Headers: Similarly, this response should contain a list of headers that will be present in the actual response to the call and should be made available to the client. All other headers will be restricted.
  • Access-Control-Allow-Credentials: This header is only required to be present in the response if your server supports authentication via cookies. The only valid value for this case is true.
  • Access-Control-Max-Age: The maximum duration that the response to the preflight request can be cached before another call is made

The response would then be examined by the browser to decide whether to continue with the request or to abandon it.

So a response to the earlier example might look like this:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Vary: Access-Control-Request-Headers
Access-Control-Allow-Headers: Content-Type, Accept
Content-Length: 0
Date: Fri, 05 Apr 2019 11:41:08 GMT
Connection: keep-alive

The Access-Control-Allow-Origin header, in this case, allows the request to be made from any origin, while the Access-Control-Allow-Methods header describes only the accepted HTTP methods. If a given HTTP method is not accepted, it will not appear in this list.

In this example, Access-Control-Allow-Headers echos back the headers that were asked for in the OPTIONS request. This indicates that all the requested headers are allowed to be sent. If for example, the server doesn't allow the Accept header, then that header would be omitted from the response and the browser would reject the call.

Handling CORS Error

The best way is to follow CORS standards and define a set of headers that allow the browser and server to communicate about which requests are (and are not) allowed. This is the best-case scenario — you should be able to implement the proper CORS response on the server which you’re calling. If the API is using express for node you can use the simple cors package. If you want to make your site properly secure, consider using a whitelist for the Access-Control-Allow-Origin header.

For local development, there are some hacks:

  • Plugins for chrome by which browser bypasses all CORS filters.
  • One more way is to start chrome with a flag of disable-web-security. That particular web session will be without CORS. This is actually bypassing the web security that chrome has.
chrome --disable-web-security --user-data-dir

More about CORS

If you wish to learn more about CORS details I recommend checking out the detailed MDN article.

I hope you have found this useful. Thank you for reading :)

Web developer who loves to code and help others code :)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store