Let’s Talk About CORS

Karen White
BigCommerce Developer Blog
8 min readJun 4, 2019

I first learned about CORS the way I learn about a lot of things in web development: by copy/pasting an error message into Google.

While the exact details of what I was trying to accomplish are a bit fuzzy, I do remember being stopped in my tracks by the error message appearing in my browser console:

“No ‘Access-Control-Allow-Origin’ header is present on the requesting resource.”

I’d stumbled across the same-origin policy, which controls the way you can make requests from one location to another in JavaScript. CORS is a way to whitelist requests to your web server from certain locations, by specifying response headers like ‘Access-Control-Allow-Origin’. It’s an important protocol for making cross-domain requests possible, in cases where there’s a legitimate need to do so.

Luckily, since then I’ve learned a lot about what CORS is, why it’s important, and how to handle making cross-origin requests as a developer. In this post, I’ll share some of those insights in the hopes that it helps another future dev copy/pasting their error message into Google.

What is CORS?

But first, let’s establish a few definitions. What is CORS anyway? CORS stands for Cross Origin Resource Sharing, and it’s a protocol that allows servers to receive requests from different domains. To understand why CORS is necessary, it first helps to understand why it would be a problem to make a request from one domain to another in the first place.

Cross-domain requests in JavaScript are restricted by the same-origin policy, which is a security standard enforced by the browser. It states that scripts loaded on one domain can only request resources that originate from the same domain. The purpose of the same-origin policy is to prevent attacks by malicious scripts.

When we talk about cross origin requests, we’re usually talking about requests from one domain or subdomain to a different domain or subdomain. But different protocols (for example http vs https) or different ports can also constitute different origins.

The same origin policy mainly comes into play when using methods like fetch or XMLHttpRequest to make an AJAX request. If you’re running a script on domain-one.com that calls out to a resource that’s also located on domain-one.com, you’re in the clear. But what if the resource you need is located on domain-two.com? Unless domain-two.com has enabled CORS to give domain-one.com permission to access its resources, the request will be blocked.

Cross-domain request example

Let’s take a look at an example, based on the BigCommerce Storefront API. We can use the Storefront Cart API to see what’s in the current shopper’s cart and add or remove items. To use it, we can make a client-side request with fetch because the endpoint for the Storefront Cart API also resides on https://www.example.com, the same domain as the store where we’re running the script:

API endpoint: https://www.example.com/api/storefront/cart

fetch('/api/storefront/cart', {
credentials: 'same-origin'}
)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(JSON.stringify(myJson));
});

CORS doesn’t come into play at all here, because we’re adhering to the same-origin policy. But what if I wanted to make a GET request to a different BigCommerce API in my client-side app, for example, from the Catalog API?

Note that the host for the BigCommerce catalog domain is api.bigcommerce.com, but we’re running the script from www.example.com. What happens if we try a request like this?

API endpoint: https://api.bigcommerce.com/stores/{store_hash}/v3/catalog/products

fetch('https://api.bigcommerce.com/stores/store_hash/v3/catalog/products', {
credentials: 'include',
headers:{
'X-Auth-Client': 'xxxxxxxxxxxxxxxxx',
'X-Auth-Token': 'xxxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
'Accept': 'application/json'
}})
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(JSON.stringify(myJson));
});

When we run the script, we see the familiar CORS error, indicating that the request was blocked:

Because we’re making a request to a different domain from the browser, the same-origin policy kicks in and the request fails.

Let me pause for a moment and note that there’s another very good reason not to structure your request this way — it exposes your API credentials in the browser. That would be a super insecure practice that would leave the door unlocked for anyone to access your data. But, let’s say that we’re trying to access a web service on a different domain where authentication isn’t an issue. In the next section, we’ll explore a few different scenarios for making cross origin requests.

Enabling CORS

Let’s consider scenario one: you control the server for the remote resource that you’re requesting. That’s good news! That means that you can enable CORS on your server and access the resources you need from any origins you grant permission to.

A server that’s been configured to support CORS returns a few different headers to let the browser know whether cross-domain requests are allowed. The most important is the Access-Control-Allow-Origin header, which defines the domains that are allowed access.

You can specify a wildcard, meaning all domains are allowed:

Access-Control-Allow-Origin: *

Or, you can restrict access only to certain domains (which is a good idea):

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

When a web page makes an Http request for a remote resource, the browser runs a preflight check to verify whether resource sharing is allowed on the destination server. The preflight request uses the Http method OPTIONS, and the options request uses three headers: Access-Control-Request-Method, Access-Control-Request-Headers, and Origin. The remote server returns a series of CORS headers that lets the browser know which request methods are allowed and from which domains. Based on that, the browser will either let the request go through, or block it.

Not all requests require a CORS preflight request; for example, if a request meets certain conditions that categorize it as a simple request, the browser won’t trigger a CORS preflight. But as a developer, you don’t need to worry about triggering a preflight request yourself — the browser takes care of it in the background.

The exact steps to configure CORS on your server will vary depending on the platform. For a Node app running on ExpressJS, for example, you can use the CORS middleware package and configure your CORS headers with just a few lines of code. Full instructions for a variety of platforms can be found at https://enable-cors.org/server.html.

CORS HTTP response headers

The following headers are available to configure CORS access on your server:

Source: MDN

What to Do When Enabling CORS Is Not an Option

Now for scenario two: you want to make a request from the browser to a resource on a server that you don’t control; for example, a public API that isn’t CORS-enabled. This is a pretty common scenario when calling a public API from your client-side app. What options do you have to make a cross origin request when you can’t enable CORS?

Use a proxy

Keep in mind that the same-origin policy only comes into play only when making a request from the browser using JavaScript. You can request resources from different origins all day long — as long as you’re making that request from a server. A proxy is a layer that sits between your request and its destination, obscuring the request’s origin. One solution for making cross-origin requests is to use a CORS proxy to make it seem as though you’re making the request from a location that’s allowed.

There are multiple CORS proxies out there that you can use for free. Two of the most well known are cors-anywhere and crossorigin.me. Crossorigin.me is easy to use — you simply prepend https://crossorigin.me/ to your request URL — but it’s not a tool that you want to use in production. Free proxies can be great for testing, but you wouldn’t want to rely on a free third party tool for something that you’ll actually be using in production. In those cases, you’ll want a more stable solution to call the API from a server and make the data available client-side.

Use a serverless function

A serverless function is another way to proxy your requests, but instead of relying on a free third-party service of unknown reliability, you’re building your own micro-infrastructure to call a web service and feed data to an API endpoint. Popular options for serverless functions include GCP or AWS, and they allow you to stake out just enough server space to run a function or two. That’s all we need in this case, because we really just need to do one thing: run a function that calls a webservice to return some data.

Let’s imagine that we’ve written a function to call the BigCommerce catalog API, and we plan to run it as an AWS serverless function. This solves 2 problems: 1) We don’t have to worry about exposing API credentials, because they’re safely stored server-side, and 2) We don’t have to worry about CORS errors when making the request to the BigCommerce API because again, server-side.

We do still need to get that data to our client-side JavaScript, however. The way we can do this is by configuring an Amazon API Gateway endpoint that acts as a trigger. When our client-side JavaScript sends an HTTP request to the API gateway endpoint, we want it to run the function and send back the response from the BigCommerce catalog API. But, now we have to think about CORS again, because the request from our JavaScript to the Amazon API Gateway endpoint goes from the browser to — you guessed it — a different origin. But, in this scenario, we control the server that’s receiving the request. We can configure our Amazon API Gateway with CORS headers that say it accepts requests from all domains (*) or just the domain on which we’re running the client-side script.

If you’re interested in learning more about using serverless functions with BigCommerce, I highly recommend Brian Davenport’s tutorial: How to Proxy the BigCommerce API Using Serverless Functions and Amazon API Gateway.

Conclusion

As frustrating as it can be to run into CORS errors, the same-origin policy is actually a very good thing — it prevents malicious scripts from accessing data that they shouldn’t have access to. Fortunately, CORS exists to make sure we can still safely make requests from the browser when needed, allowing developers to get data that provides richer user experiences.

If you found your way to this article via a Google search for an error message, I hope this has helped resolve some questions — or at least shown you some strategies for handling cross-origin requests. Let us know what you thought about this article by commenting below or send us your questions by tweeting @BigCommerceDevs.

--

--

Karen White
BigCommerce Developer Blog

Developer Marketing at Atlassian. Formerly at BigCommerce, Rasa. I think Voyager was the best Star Trek.