An In-depth Look at CORS

Share this article

This article was peer reviewed by Panayiotis «pvgr» Velisarakos. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
CORS is a relatively new API that came with HTML5 which allows our websites to request external and previously restricted resources. It relaxes the traditional same-origin policy by enabling us to request resources that are on a different domain than our parent page. For example, before CORS cross-domain, Ajax requests were not possible (making an Ajax call from the page example.com/index.html to anotherExample.com/index.html). In this article we’ll see how to use CORS to further interact with other systems and websites in order to create even better Web experiences. Before exploring CORS more, let’s first have a look at which browsers support it.

CORS and Browser Support

Internet Explorer 8 and 9 support CORS only through the XDomainRequest class. The main difference is that instead of doing a normal instantiation with something like var xhr = new XMLHttpRequest() you would have to use var xdr = new XDomainRequest();.
IE 11, Edge and all recent and not-really-recent versions of Firefox, Safari, Chrome, Opera fully support CORS. IE10 and Android’s default browser up to 4.3 only lack support for CORS when used for images in <canvas> elements. According to CanIuse, 92.61% of people globally have supporting browsers which indicates that we are likely not going to make a mistake if we use it.

Making a Simple Cross-Origin Ajax Request

Now that we know that the same-origin policy prohibits websites in different domains from making Ajax requests to other domains, let’s see how we can bypass this in order to make a cross-origin Ajax request to another website. If you simply try to shoot an Ajax request to a random website, it would most likely not be able to read the response unless that another website allows it.
<script>
    var xhr = new XMLHttpRequest();
    var url = "//example.com";
    xhr.open("GET", url);
    xhr.onreadystatechange = function() {

        if (xhr.status === 200 && xhr.readyState === 4) {

            document.querySelector("body").innerHTML = xhr.responseText
        }
    }
    xhr.send();
</script>
If you open your browser’s console, you would get a message similar to:
XMLHttpRequest cannot load http://example.com. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://otherExampleSite.com‘ is therefore not allowed access. To successfully read the response, you would have to set a header called Access-Control-Allow-Origin. This header has to be set either in your application’s back-end logic (setting the header manually before the response is delivered to the client) or in your server’s configuration (such as editing apache.conf and adding Header set Access-Control-Allow-Origin "*" to it, if you are using Apache). Adding the header with the meta tag in your document’s <head> tag like this would not work: <meta http-equiv="Access-Control-Allow-Origin" content="*"> Here is how you can enable cross-origin requests for all origins (sites that request the resource) in PHP:
< ?php
header("Access-Control-Allow-Origin: *")
?>
When making cross-origin requests, the destination website has to be the one who has your origin enabled and allows you to read the response from the request. If you want to allow a specific origin you can do something like this in PHP:
header("Access-Control-Allow-Origin: http://example.com");
However, the Access-Control-Allow-Origin
header itself does not allow multiple hosts inserted in the header, no matter the delimiter. This means that if you want to allow a cross-origin request from various different domains, you have to dynamically generate your header. For example, in PHP you can check the origin of the website requesting your resource and if it matches a particular whitelist, add a header that allows that specific origin to make a cross-origin request. Here is a tiny example with a hardcoded whitelist:
< ?php
$allowedOrigins = array("http://example.com", "http://localhost:63342");
$headers = getallheaders();
if ($headers["Origin"] && in_array($headers["Origin"], $allowedOrigins)) {
    header("Access-Control-Allow-Origin: " . $headers["Origin"]);
}
?>
Some safety is maintained in the cross-origin request and the credentials (such as cookies) are not leaked during the request-response exchange. Furthermore, if the remote server does not specifically allow the user credentials for its website to be included in a cross-origin request from another website and that website does not explicitly declare it wants the user credentials to be passed to the remote server, then the site making the request will most likely get a response that is not personalized. This happens because the user session cookies would not go to the request and the response will not contain data relevant to a particular logged-in user which reduces CSRF and other exploits. To make things simpler, let’s say that we have two websites. The first one sets a cookie and whenever the user enters, it shows the cookie’s value which is supposed to be his name. The other website makes a cross-origin Ajax request and adds the response to its DOM. We set a cookie with the user name and display it with each visit We see that the website making the cross-origin Ajax request does get the default view of the page, with no personalization due to cookies and sessions

Getting the Page as the User Sees It with CORS

If we want to include the user credentials with the remote request, we have to do two changes, the first in the code of the website making the request and the second in the website receiving the request. In the website making the request we have to set the withCredentials property of the Ajax request to true:
var xhr = new XMLHttpRequest();
 xhr.withCredentials = true;
The remote server itself, besides allowing our origin, has to set an Access-Control-Allow-Credentials header and set its value to true. Using the numbers 1 or 0 would not work. If we simply set withCredentials to true but the server has not set the above mentioned header, we won’t get the response, even if our origin is allowed. We will get a message similar to:
XMLHttpRequest cannot load http://example.com/index.php. Credentials flag is ‘true’, but the ‘Access-Control-Allow-Credentials’ header is ”. It must be ‘true’ to allow credentials. Origin ‘http://localhost:63342‘ is therefore not allowed access. If both changes are made, we will get a personalized response. In our case, the user’s name which we stored in a cookie will be in the response that the remote server returns to our website.
We get a response from the remote server based on a request with the user credentials passed. In other words, we get the page as the user sees it. However, allowing the credentials to be passed to a cross-origin request is quite dangerous, since it opens up the possibility for various attacks such as CSRF (Cross-Site Request Forgery), XSS (Cross-Site Scripting) and an attacker could take advantage of the user’s logged in status to take actions in the remote server without the user knowing (such as withdrawing money if the remote server is banking website).

Preflights

When requests start getting more complicated, we may want to know if a particular request method (such as get, put, post, patchor delete
) or a particular custom header is allowed and accepted by the server. In this case, you may want to use preflights where you first send a request with the options method and declare what method and headers your request will have. Then, if the server returns CORS headers and we see that our origin, headers, and request methods are allowed, we can make the actual request (Origin is a header that is passed by our browsers with every cross-origin request we make. And no, we cannot change the Origin’s value when making a request in a typical browser). Response to an OPTIONS method in a preflight request As we can see in the image above, the server returns several headers which we can use in determining whether to make the actual request. It returns to us that all origins are allowed (Access-Control-Allow-Origin: *, that we cannot make the request while passing the user credentials (Access-Control-Allow-Credentials), that we can only make get requests (Access-Control-Allow-Methods) and that we may use the X-ApiKey custom header (Access-Control-Allow-Headers). Lastly, the Access-Control-Max-Age headers shows the value in seconds, pinpointing how long (from the time of the request) we can make requests without relying on another preflight. On the other hand, in our front-end logic we pass the Access-Control-Request-Method and we pass the Access-Control-Request-Headers to indicate what kind of request method and what kind of headers we intend to add to our real request. In Vanilla JavaScript, you can attach a header when making Ajax calls using the xhr.setRequestHeader(‘headerString’, ‘headerValueString’);.

CORS for Canvas Images

If we want to load external images and edit them in canvas or just save their base64 encoded value in localStorage as a cache mechanism, the remote server has to enable CORS. There are various ways this can be done. One way is to edit your web server’s configuration to add the Access-Control-Allow-Origin header on every request for specific image types, such an example is shown in the Mozilla docs. If we have a script which dynamically generates images by changing the Content-Type and outputs an image such as we can simply set that header along with outputting the image.
Without CORS, if we try to access a remote image, load it in canvas, edit it and save it with toDataURL or just try to add the modified image to the DOM with toDataURL, we will get the following security exception (and we will not be able to save or show it): Image from origin ‘http://example.com‘ has been blocked from loading by Cross-Origin Resource Sharing policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:63342‘ is therefore not allowed access.
If the server where the image is returns the image along with a Access-Control-Allow-Origin: * header, then we can do the following:
var img = new Image,
    canvas = document.createElement("canvas"),
    ctx = canvas.getContext("2d"),
    src = "http://example.com/test/image/image.php?image=1";
    img.setAttribute('crossOrigin', 'anonymous');
    img.onload = function() {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage( img, 0, 0 );
        ctx.font = "30px Arial";
        ctx.fillStyle = "#000";
        ctx.fillText("#SitePoint",canvas.width / 3,canvas.height / 3);
        img.src = canvas.toDataURL();
        document.querySelector("body").appendChild(img);
        localStorage.setItem( "savedImageData", canvas.toDataURL("image/png") );
    }
    img.src = src;
This will load an external image, add a #SitePoint text in it and both display it to the user and save it in localStorage. Notice that we set a crossOrigin attribute of the external image – img.setAttribute('crossOrigin', 'anonymous');. This attribute is mandatory and if we do not add it to the external image we will still get another security exception.
Cors-Ca

The Crossorigin Attribute

When we make requests for external images, audio, video, stylesheets and scripts using the appropriate HTML(5) tag we are not making a CORS request. This means that no Origin header is sent to the page serving the external resource. Without CORS, we would not be able to edit an external image in canvas, view exceptions and error logging from external scripts that our website loads, or use the CSS Object Model when working with external stylesheets and so on. There are certain cases when we want to use those features and this is where the crossorigin attribute that we mentioned above comes in handy. The crossorigin attribute can be set to elements such as <link>,<img> and <script>. When we add the attribute to such an element, we make sure that a CORS request will be made with the Origin header set properly. If the external resource allows your origin through the Access-Control-Allow-Origin header the limitations to non-CORS requests will not apply. The crossorigin attribute has two possible values:
  1. anonymous– setting the crossorigin attribute to this value will make a CORS request without passing the user’s credentials to the external resource (similar to making an Ajax CORS request without adding the withCredentials attribute).
  2. use-credentials – setting the crossorigin attribute to this value will make a CORS request to the external resource along with any user credentials that might exist for that resource. For this to work, the server must not only set an Access-Control-Allow-Origin header that allows your Origin but it must also set Access-Control-Allow-Credentials to true.
User credentials include cookies, HTTP Basic Auth credentials, certificates and other user data that are sent when the user requests a specific website.

Conclusions

CORS enables developers to further interact with other systems and websites in order to create even better Web experiences. It can be used along with traditional requests made by popular HTML tags as well as with Web 2.0 technologies like Ajax. Have you been using CORS in your projects? Did you have difficulties with it? We would like to know what your impressions of it are so far.

References and Further Reading:

Frequently Asked Questions (FAQs) about Cross-Origin Resource Sharing (CORS)

What is the purpose of the Access-Control-Allow-Origin header in CORS?

The Access-Control-Allow-Origin header is a crucial part of CORS. It specifies which origins are allowed to read the response from the server. This is important because it prevents unauthorized sites from accessing sensitive data. The value of this header can either be a specific origin or ““, which allows all origins. However, using “” is not recommended for sensitive data as it can lead to security issues.

How does CORS handle cookies?

CORS handles cookies through the Access-Control-Allow-Credentials header. If this header is set to true, it allows the server to access cookies from the client-side. However, when this header is used, the Access-Control-Allow-Origin header cannot be set to “*”, it must specify an origin. This is to ensure the security of the data.

What is the difference between simple and preflighted requests in CORS?

Simple requests are those that do not trigger a CORS preflight. These are certain types of GET, HEAD, or POST requests. On the other hand, preflighted requests are those that first send an HTTP OPTIONS request header to the resource on the other domain, in order to determine whether the actual request is safe to send.

How can I enable CORS on my server?

Enabling CORS on your server depends on the server-side language and framework you are using. Generally, it involves setting the appropriate headers in the response. For example, in Node.js with Express, you can use the cors middleware to enable CORS.

What are the potential security risks associated with CORS?

While CORS is designed to enhance security, if not implemented correctly, it can lead to security risks. For instance, if the Access-Control-Allow-Origin header is set to “*”, it can allow malicious sites to access sensitive data. Also, if the Access-Control-Allow-Credentials header is not handled properly, it can lead to data leakage.

Can I use CORS with all types of HTTP requests?

Yes, CORS can be used with all types of HTTP requests. However, certain types of requests, like PUT or DELETE, or requests with certain MIME types, will trigger a preflight request to ensure that the server will accept the request.

What is the role of the Access-Control-Request-Method header?

The Access-Control-Request-Method header is used in preflight requests to let the server know which HTTP method will be used when the actual request is made. This allows the server to determine whether it will accept the request based on the method.

How does the browser handle CORS?

The browser automatically handles CORS by adding the Origin header to the request and checking the Access-Control-Allow-Origin header in the response. If the origin in the response header matches the origin of the request, the browser allows the response data to be shared with the client-side script.

Can I disable CORS for testing purposes?

Yes, you can disable CORS for testing purposes, but it’s not recommended for production environments due to security risks. You can disable CORS in your browser settings or by using certain browser extensions.

What is the difference between CORS and JSONP?

CORS and JSONP are both techniques for making cross-origin requests, but they work differently. CORS uses HTTP headers to tell the browser to allow cross-origin requests, while JSONP works by including a script tag in the HTML that loads a script from a different origin. JSONP only supports GET requests, while CORS supports all types of HTTP requests.

Ivan DimovIvan Dimov
View Author

Ivan is a student of IT, a freelance web developer and a tech writer. He deals with both front-end and back-end stuff. Whenever he is not in front of an Internet-enabled device he is probably reading a book or travelling.

ajaxAPIsAurelioDcorsjavascript
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week