What is it, and what is its role?
Going back to the early times of web technology, more precisely, beginning with the year 1995 the concept of the same-origin policy was
introduced. The same-origin policy protects against reusing authenticated sessions across origins. What is the origin? In simple terms,
it’s the URL. Imagine you have a server running on http://localhost:5000, that is the origin. Let's get through a scenario, assume that
a user is visiting a banking website and doesn't log out. Then, the user goes to another site that has a malicious JavaScript code
requesting data from the banking site. Because the user is still logged in on the banking site, the malicious code could do anything the
user could do on the banking site.
You can read more about the same-origin policy
here, from
where the example mentioned above was taken
.
The ideal scenario is to get any resource from other origins, because this allows for greater interaction, although not possible with the
same-origin policy in place. Here comes JSONP in context, a JavaScript technique for requesting data by loading an <script> element
which is an element intended to load ordinary JavaScript. JSONP enables sharing data, bypassing the same-origin policy, but it does
require permission from the server that has made requests to it, in order for this to work.
How is it configured?
You would need a JavaScript file containing a callback function that accepts some data, then in your HTML, a script element containing
the following:
With this, you would have access to the response data, through your callback function, but there was a problem with JSONP: it's vulnerable
to data source replacing of the function call with malicious code, which is why it was replaced by CORS but even to this day this
technique is not fully depreciated, some servers still allow its use.
More information on JSONP
here.
End of the history lesson
As stated above, CORS replaced JSONP and it is the official solution that allows cross-origin resource sharing, its role is to offer a way
for developers, to set up safe cross-origin sharing through a number of settings it offers and thus, making same-origin policy happy so to
speak.
As an example, for a real-life scenario:
You and your friend live in a 2 apartment building, and thanks to this you can communicate with each other without a problem. This is the
normal interaction between the frontend and backend that is running on the same origin. Now the building has a guard, it is called the
same-origin policy, and won't allow any unknown person to call your apartment, but you are allowed to bypass this, by using a strict
protocol called CORS. By conforming to the rules and setting up CORS, you can allow other people to call your apartment. With CORS you
can, for example, set up a list of trusted people that are allowed to call your apartment. A list of allowed interactions with people,
like receive gifts, have modifications done to your apartment, etc. So through this CORS protocol, you would be able to interact with
trusted people or even allow anyone to do anything, it all depends on how you set it up.
Regarding technical details, there needs to be some knowledge about headers and how they interact with requests. Headers are the
guidelines between the client and the server, where you can find, for example, the type of files to be expected like text or
application/JSON, the language to be expected, encoding, and much more. Here are some resources to get to know headers better:
So, why are headers needed and when do they come into action?
Most likely you will find out by checking out the material I provided for both, the one making the request and the one sending the
response use headers, so based on what the request headers contain, CORS will react in an appropriate manner and the backend will respond
with its headers of which you will find the
Access-Control Headers so to speak.
Access-Control-Allow-Origin
This is the first barrier that checks to see if the origin of the request is the same as the backend. In case they are the same, it goes
through with the request. If not, then you will most likely get this error:
So what does this mean?
In the first place, the origin doesn’t do match, therefore the backend didn't respond with the
"Access-Control-Allow-Origin" and because of this, further interaction was blocked. There is a suggestion to "turn off" CORS but we don't
want that because an opaque response does not serve our needs.
To get around this, the server owner must set another origin on the Access-Control-Allow-Origin header. For this, we have specific
settings to go over on the backend, and by using the Express framework we have the following way of setting things up:
Side note
If you have not used Node.js and Express with its CORS middleware, have a look at these resources for a better understanding of what is
going on:
We set up an object containing the settings that we need as we go on. As you can see at the moment the origin is set to “localhost:5000”,
well let's say Google is an origin we want to trust, so we do this:
As you can see we added Google to our list of trusted origins, but there are a few more things to set up.
Here we have the response for GET requests set up and like in the example, we have to place our settings in a specified location.
The origin key can have multiple values assigned to it, you can limit it to your origin or add trusted origins via a whitelist. This is
just an array of trusted origins you want to be able to access your API. You can also set it to everyone via the wildcard "*":
Or have the origin be set in a dynamic fashion, meaning any origin that makes a request to your backend will be set as a value for origin:
Access-Control-Allow-Methods
Side note, Express CORS middleware default settings are as follows:
In order to limit the request methods to a specific number you have to set things up on the backend:
Single request method:
or in the case of a list:
As the name implies the fact that it deals with the request methods. The default GET request is always allowed, even if you limit it to
methods that do not include it because it does not cause modifications on the backend, and on this type of request important information
is not exchanged (if everything is set correctly). Generally, CORS starts to differentiate when other types of methods are used like
PATCH, for example, that is used to update data on the backend, and at this point, CORS comes into play and makes what is called a
"Preflight Request" using a method called OPTIONS:
This is done automatically upon detection of a request that is not within the standard request type. As you can guess this checks with the
backend to see if the method used is allowed and if the answer is “yes”, then it proceeds ahead with the request, at this point the
backend will have in its response headers Access-Control-Allow-Methods "PATCH" or a list of allowed methods depending on how you set
things up:
If the request fails because the list of allowed methods on the backend does not contain the used method you will get the following error:
Access-Control-Allow-Headers
As before with requests, there is this section that deals with headers. There are some default headers already set that are allowed, like
Content-Type or Language, but what if the request has its own custom headers? Well CORS won't stand for that. One of the reasons is that
HTTP requests smuggling, which can be used to bypass front-end security controls. Find out more about this subject
here. This is another trigger for the preflight method
called OPTIONS to check with the backend if the custom header is allowed in requests. You can already imagine at this point the two
possible outcomes, if the custom header is not present in the CORS settings you will find the following error:
Setting it up correctly:
With this everything will work as expected and here are the response headers:
Access-Control-Allow-Credentials
Cookies, everybody loves them, but don't exaggerate! Anyway, Access-Control-Allow-Credentials deals with cookies and the authorization
header. There are not that many options regarding this header. All you need to do is set credentials to true or false in the CORS settings
and you are all set:
Well as long as you don't set Access-Control-Allow-Origin to the wild card "*" when using credentials this is not allowed. You must have
either a set origin or an array of allowed origins, like the whitelist I mentioned before otherwise, it won't work and you will find
yourself with the following error:
The response headers after setting it up correctly:
Access-Control-Expose-Headers
The number of headers you can select with JavaScript is limited to self listed response headers set up by CORS, and they are as follows:
So how can we expose other headers? Back in our CORS settings, you can specify what headers you want to expose through comma-separated
values:
Now we have -date- and -etag- headers exposed and can be selected through JavaScript.
The reason why headers are not exposed by default is that JSONP, even if it was a solution for the same-origin policy, the headers were
never exposed in the process so as to keep this assumption, CORS by default does not expose all the headers unless you set it to.
Acces-Control-Max-Age
This allows us to cache the results of a preflight request by setting up the following:
With this, we have set the cache time to 20 seconds, this means that our following requests do not trigger the OPTIONS preflight request
as long as we don't change our request settings.
The response headers are as follows:
Conclusion
When I first encountered CORS, I was annoyed and could not really understand it, but when you have something you don't understand, it's
worth looking into it before you decide to just hate it. Personally, after writing this article, I have come to understand a lot more
about CORS and same-origin policy. Now I get why it exists. Sometimes we need to be able to share resources cross-origin and CORS is there
to help us do it. This being said, I hope this article sheds some light on CORS, and next time you see a CORS error to know why it is
there and how to fix it.