Before pushing your code to a server, you decide to test whether everything is working correctly. Your Vue app is running at
http://localhost:3000 while the API server is running at
http://localhost:8000. You visit
http://localhost:3000 in your web browser and you notice a bug. No data has been loaded from the API! Oh No!
You stay calm. You try to debug the situation. Like any good web developer you look in the browser’s DevTools for the answer.
And the first thing you see is the following error in the console.
And here is what you get:
I don’t even remember the number of times I found myself in the same situation. I would always google the error and get some StackOverflow answer telling me to add CORS support. And I would do that by adding an
Access-Control-Allow-Header:* to the HTTP response on the back-end. This would solve the issue. However I never really tried to understand the actual reason behind this error.
Recently, I decided to get to the bottom of this mystery once and for all. So I picked up CORS in Action and studied CORS in depth. In this post, I will walk you through what I understand to be the gist of the CORS mechanism.
Difference between the Browser and the Client
Now, before we start this discussion, I would like to clarify some technical terms.
The browser and the client are different things when we are talking about AJAX calls.
Shocking right? I know.
Before I explain the same-origin policy, let’s have a look at what an origin is and how it works.
What is an Origin?
The origin in an HTTP request comes from the webpage the JS script resides on. It is an HTTP header. Its value is the combination of a scheme, host and port from the URL. For example, in the following URLs
The URL scheme or protocol is the first part in the URL, before the
://. So in our case, the schemes would be
https. The host is defined as the IP address or the domain, so in our case that would be
alazierplace.com. And finally the port is the number after the colon, i.e.
3000. In the case where no port is specified then the default HTTP ports are used i.e.
80 for HTTP URLs and
443 for HTTPS URLs. So the origin for the the URLs above are as below respectively:
The issue we face in the above scenario is due to the same-origin policy implemented by most browsers we use today. This policy dictates that a script or document loaded from one origin cannot freely interact or request data from other origins. By default a web page loaded from the origin
https://alazierplace.com can only request resources (css, jpeg, or png files etc.) from the same origin or server i.e.
So, when you open my website, the first thing your browser will request is the HTML. The HTML contains a link to a CSS stylesheet which has the following URL
This request for the CSS file succeeds because the origin of the CSS file is the same as the web page requesting the file.
If the CSS file was hosted at another origin like for example
Then the browser would not let that request succeed because it is requesting a resource from a different origin. This is a critical security measure which reduces the chances of malicious scripts and documents harming users and other websites. The important point to note is that, the same origin policy is a purely client side measure that is implemented in web browsers.
Now you may ask, that APIs usually are hosting on different origins than the main web app and the API calls and requests succeed, so how does that work?
Good question. The short answer to that is the CORS mechanism, which allows servers to control which origins can request resources from them and which cannot.
CORS — Cross-Origin Resource Sharing
CORS is short for Cross-Origin Resource Sharing. It is a web standard recommended by the W3C that enables web clients i.e. scripts running in the browser to access content from origins other than their own.
Web servers, through the use of HTTP response headers, can decide which origins to serve or reject, which HTTP methods to allow as well as other options. Web Browsers that implement the CORS policy will then check the CORS related response headers and act accordingly. CORS puts the control of the content in the hands of the servers.
Cross-Origin Request Cycle
So, the basic flow when making a cross-origin request is illustrated below:
Okay, so let’s break that down shall we?
- The JS Client initiates an AJAX call.
- The browser intercepts the request, and before sending this request to the server, it sends a pre-flight request. A pre-flight request is an HTTP Options request.
- The response of the pre-flight request contains HTTP headers containing the CORS configuration of the server.
- Once, the CORS policy has been verified through the pre-flight request, then the actual request is sent to the server.
- The server returns the response which is then delivered to the client by the browser.
The interesting thing to note here is the pre-flight request. It is simply an HTTP OPTIONS request and its response contains HTTP headers that specify the CORS configuration. The pre-flight request is sent only once and is then cached by the browser.
The pre-flight request is not sent on every CORS request. It is sent only when the following criteria is met:
- The request uses HTTP methods other than
Content-Typerequest header has a value other than
- The request sets headers other than
These are collectively known as simple methods and simple headers.
Here are the HTTP response headers used in CORS:
- Access-Control-Allow-Origin — The most popular CORS header because it is the first to appear in most errors. It’s value can either be a single origin or the wildcard
*value. If a single origin is listed, than only scripts from that origin can request the server for resources. If a wildcard
*value is supplied, than the server accepts requests from any origin. This is useful for creating public APIs.
- Access-Control-Expose-Headers — This header lists headers that are white-listed and can be accessed by the client. The server sends these headers in the response.
- Access-Control-Allow-Headers — This header lists which HTTP headers are allowed in the actual request.
- Access-Control-Allow-Methods — This header lists which HTTP methods are allowed by the server when the resource is accessed.
- Access-Control-Allow-Credentials — This header indicates whether the actual request can contain credentials i.e. cookies and other information that can be used to identify the user.
- Access-Control-Max-Age — This header contains the number of seconds that the pre-flight request remains cached in the browser. If the time has expired then another pre-flight request is made on the next AJAX call.
Putting it all together
As with everything, there are a few caveats and precautions that should be kept in mind while dealing with CORS configurations.
- Pre-flight requests can introduce some problems with caching when the site is served behind a load balancer or proxy server. In such cases the origin of the request can vary, so pre-flight requests are not properly cached. This can be mitigated by adding the
Originheader to the
- The same-origin and the CORS policies are implemented by web browsers. If the user is using a web browser which does not support these mechanisms, then the CORS request will work as a normal HTTP request.
- CORS is not the only way to do cross-origin communication. JSONP, postMessage and server side requests are all way in which a browser’s CORS policy can be bypassed.
I read a complete book just to prepare this blog post. And that book was CORS in Action by Monsur Hossain. It is an exceptional book and the code samples in it explain CORS in an excellent manner.
To all the web developers looking to take a deeper dive into cross-origin communication, definitely give Monsur Hossains’s book a read. It is well worth the investment.
Originally published at https://alazierplace.com on June 28, 2019.