Understanding Cross Origin Resource Sharing (CORS) is important from the security standpoint. The reason is explained below.
Rich web experiences use a lot of Web API, CDN caching, and cross domain requests for resource sharing. Supporting CORS from the server not only helps in retaining maximum control on who can access server resources, but also ensures that application servers cater to the maximum audience possible that need to access server resources -something that is important for the growth of any business. Thus, understanding how to securely use and support CORS is paramount.
To understand CORS, we must study Same-Origin Policy (SOP). Web browsers implement and enforce SOP to ensure that only content coming from the same origin is able to make requests and access user data like cookies, local storage, DOM and so on.
The absence of an SOP will create chaos and mayhem, as scripts from any origin will be able to read user data and make requests (read: perform unauthorized actions).
To understand this, let’s take an example. What if Ciitbaank.cc can read your account balance of your net banking account at citibank.com? Or, Fabekook.cn can post anything to your facebook.com account.
Sounds scary, right?
To prevent these sort of situations, browsers implement and enforce an SOP. The browser decides on four parameters to verify whether a request originates from the same domain or from a cross domain. The parameters are:
- Origin Header
To understand how Protocol, Host, and Port play a role in determining the origin, please see the excellent explanation here. As a ready-reckoner, we’ve pulled out the the origin determination rules tables from the link and placed them below.
To illustrate, the following table gives an overview of typical outcomes for checks against theURL “http://www.example.com/dir/page.html”.
We will look at the Origin header parameter in detail.
The Origin header is central to CORS. The client identifies itself to the server by using the Origin header. A CORS request must have an Origin header; there is no way around it. If there is no Origin header, it is not CORS.
The browser is solely responsible for setting the Origin header. It adds the Origin header to the HTTP request before sending the request to the server. The Origin header is always present on cross-origin requests, and the client cannot set or override the value. This is a requirement from a security standpoint: if the client could change the Origin header, they could pretend to be someone they are not.
The figure below shows how the browser adds the Origin header before sending the request to the server.
NOTE:Chrome and Safari include an Origin header on same-origin POST/PUT/DELETE requests. Same-origin GET requests will not have an Origin header.
Here’s a question: If the browser implements and enforces SOP to protect us, why do we need to understand CORS, and why do browsers support it at all?
The answer is simple. A webpage that loads on an internet browser comprises lots of data. Most of that data comes from multiple origins for various reasons. The need of reading data or requesting resources from another origin, dynamically, needs to be addressed.
Before CORS came into existence, developers worked around the browser SOP by using JSON-P(something that’s use is limited due to security concerns) or by setting up a custom proxy (an unnecessary overhead to setup and maintain).
W3C came up with the Cross Origin Resource Sharing (CORS) spec, which directly allows cross-domain requests from the browser. By using XMLHttpRequest, CORS allows developers to make cross-domain request in same way as they would make a same-domain request.
The CORS Lifecycle
The CORS lifecycle is as follows:
- Client makes a CORS request
- Browser enforces CORS
- Server support CORS (using response headers)
- Browser returns a valid response back to client.
Major Players in CORS Lifecycle:
- Server: The server supports CORS
- Browser: The browser enforces CORS on behalf of client.
Browsers that supports CORS:
The following browsers support CORS:
- Chrome 3+
- Firefox 3.5+
- Opera 12+
- Safari 4+
- Internet Explorer 8+
Types of CORS Requests
There are 2 types of CORS request:
1. Simple Request
Q: What makes a CORS request ‘simple’?
A: Any request that uses only the following methods and request headers are considered simple CORS request.
a. Simple Methods: A simple method is an HTTP method that will not trigger a preflight request. The simple methods are defined as:
Note: Requests with a simple method may still trigger a preflight request if they contain non-simple headers.
b. Simple Request Headers:
A simple header is an HTTP request header that does not trigger a preflight request. The client does not need the server’s permission (via a preflight) to make requests with only these headers. Simple headers are defined as:
- Content-Type, but only if the value is one of the following:
c. Simple Response Headers:
Simple response headers are those that are visible to the client by default. All other headers need permission from the server to be viewed on the client; the server gives permission by using the Access-Control-Expose-Headers header.
The simple response headers are defined as:
Making A CORS Request (Simple Request)
For this demo, we have setup a Node.js server, written a script to start an API server athttp://127.0.0.1:9999 that will display blog post on http://127.0.0.1:9999/api/posts, and another server at http://localhost:1111, as shown in the code below:
a. Making a CORS request (when the server does not support CORS)
1. Start the server and point your browser to http://localhost:1111/client.html as shown in the snapshot below:
2. Since the origin is different and it is a CORS request, the server does not honor this CORS request and you see an error like the below:
On revisiting the CORS Lifecycle, we have covered the first two steps:
- Making a CORS request from Client
- Browser needs to enforce CORS
- Server needs to Support CORS (using response headers)
- Browser shows the valid response back to client.
b. Making a CORS request (when server supports CORS)
By now, we know how a browser makes CORS requests. However, until now, no CORS headers were set by the server in response. Now, let us change our server code such that it allows CORS requests, as well, by setting “Access-Control-Allow-Origin = *” as shown in the code below:
Execute the changed server.js and access the same client.html fromhttp://localhost:1111/client.html, as shown in the snapshot below:
As you can see, the Origin and Host header values are different. The browser makes a CORS request, and since the server supports these requests, the server responds with the requested data that is then read by the browser.
2. Preflight Request (or not so simple CORS Request)
The server responds to the CORS requests by using the Access-Control-Allow-Origin header. While this header is required on all valid CORS responses, there are some cases where the Access-Control-Allow-Origin header alone isn’t enough.
Requests like DELETE or PUT need to go a step further and ask for the server’s permission before making the actual request. The preflight gives the server a chance to examine what the actual request will look like before it is made. The server can then indicate whether the browser should send the actual request, or return an error to the client without sending the request.
CORS introduced the preflight request model to support backward compatibility. Let’s say that your web server does not support CORS, but browsers have implemented CORS. This means that your web server will get CORS requests that it does not know how to respond to.
To avoid the element of surprise, the browser sends preflight request and ask servers if they support CORS and allow requests with that origin, containing methods and headers. If not, the browser will not make the actual request. GET, POST, HEAD and OPTIONS are all requests that server understands, so no preflight request are initiated from browser.
a. Making a Preflight CORS Request (server does not support DELETE Method)
Let us change our client.html file to make a DELETE request instead of GET request as shown in the snapshot below:
As you can see the error in the console, the client is trying to make a DELETE request that server doesn’t accept. As such, the browser does not send the request. Hence, the error.
b. Making a Preflight CORS Request (server supports DELETE Method)
Now let us change our server code to accept DELETE method as a CORS Request, as shown in the snapshot below:
Now, let us open the client.html file and click delete to see if posts gets deleted, as shown in the snapshot below:
We see that posts are indeed being deleted and that there are no errors in Console.
Now, let us confirm the same from the server URL, as shown in the snapshot below:
As you can see, posts are deleted from main server as well. This means that our cross domain DELETE method calls were successfully accepted by the server.
c. Sending Cookies to a different domain using CORS
So far, we have made simple and preflights request. However, in neither have we sent cookies in a cross-domain request. By default, CORS requests do not send or set cookies. If you need to send cookies as part of the request, you will need to set the XHR .withCredentials property to True.
As we have seen in previous demos, the server must allow Origin, Method and Headers that the client sends. Else, in the browser, the preflight response server will not make the actual request. Similarly, before sending cookies, the server must be enabled by setting the Access-Control-Allow-Credentials response header to True.
- Code examples from Hossain, Monsur. CORS in Action: Creating and Consuming Cross-origin APIs.Print.
- Hossain, Monsur. “Using CORS – HTML5 Rocks.” HTML5 Rocks. Html5rocks, 26 Oct. 2011. Web. 13 Sept. 2015. <http://www.html5rocks.com/en/tutorials/cors/>.
- “Cross-Origin Resource Sharing.” www.CanIUse.com Web. 13 Sep. 2015.