This article is the first part in a series of two articles for CORS. This part provides an introduction to CORS which will help you grasp the CORS Concept in an effortless way and allow you to better design, understand, and troubleshoot CORS issue.
In the next part, we will go into further details, write code and apply the knowledge learned in the first part to build a Client & Server application which can communicate using CORS. The link would be added for the second part once available.
In modern software development, with the advent of the microservices and the rise of the Distributed systems, more components than ever before are being developed in isolation. The monolith applications are being architectured to build smaller more manageable components in the form of the Asp.net core web API. The microservices can be deployed and scaled independently of the frontend. Also, since the Frontend needs to communicate with the API, it is the API's responsibility to ensure that it allows the clients to interact and send appropriate data to clients to enable secure communication. CORS forms a major specification which allows an API to accept multiple incoming requests from different websites.
What is CORS?
CORS abbreviation is CROSS ORIGIN RESOURCE SHARING.
Credit: Getty Images
Cors is a SECURITY mechanism employed by browsers like (Firefox, Chrome, IE etc.) to prevent the browsers from making calls to another Website. A request for a resource (like an image or a font) outside of the origin is known as a cross-origin request. CORS (cross-origin resource sharing) is a specification which manages cross-origin requests.
A more naive explanation - It's like the Security Guard which prevents a malicious person from entering your premises until they possess certain Authorization and hence keeps your family Safe.
The Analogy of an Apartment Community
Let's take an analogy to understand CORS. Imagine, You are living in a secure Housing Apartment Community and have access to all the facilities and are having a great time ๐. The access to the community is restricted to its tenants only to ensure safety. If you are a tenant, then you can come in/out at any time. For anyone else, permission is denied.
You are thinking about your birthday party in your community swimming pool and want to invite your friends. Since the access to the pool is only restricted to tenants, how would you sneak in your friends?
In order for them to enter the apartment community, you need to establish a mechanism to allow your friends to come inside the Apartment Complex. One of the mechanisms might be giving your friends some Unique Passes which the security would trust and allow access.
The above analogy was a simplistic explanation to understand the overall concept of the CORS. The notion of security for a Housing apartment is similar to the security implemented by the browsers using the same origin policy. I will refer to this example later while explaining the CORS concept in this article.
Let's understand what all this fuss is about with Origin and what exactly Origin is in the next section. This is the most important Concept to decipher CORS easily.
What is an Origin?
Let's take a look at what Origin is exactly. An Origin is made up of the following three parts:
- Protocol/Scheme : (Http/https)
- Host : The server/domain name
- Port number : The numeric value
Therefore a URL like https://galodha.com, represents an Origin.
Example of Same Origin
Let's consider the following two Url's which belongs to same origin, https://galodha.com.
https://galodha.com/image1.jpg
https://galodha.com/image2.jpg
Above Url's are having the same Protocol (https), Host (galodha.com), and Port Number (80 by default for HTTP communication).
Example of Different Origin
Following are the example of Origins which are having a different Origin than https://galodha.com. One of the Url's is having a different protocol and other belongs to a different Host.
https://galodha.com/image1.jpg (Different protocol)
https://github.com/image1.jpg (Different host)
Now, after gaining the understanding of Origin, let look at what exactly is the Same-Origin Policy.
What is meant by Same Origin Policy?
The same-origin policy is a security measure standardized among browsers. It prevents different origins from interacting with each other, to prevent attacks such as Cross-Site Request Forgery. Referring to our analogy, the Same origin is like the tenants belonging to the same apartment community. You can trust a tenant in your Apartment Community, but wouldn't trust another person in other Apartment Community, unless they are your friends.
Example: The Same Origin Allowing communication
The following image shows the communication between client and browser in the same origin. A Client browsing website https://galodha.com can make the calls to https://galodha.com/projects API to get the data. The origin (https://galodha.com) is same for the client and the server and hence communication is allowed.
Sharing resources
The same-origin policy is very restrictive. This prevents JavaScript from making requests across different Origins. Although the same-origin policy is effective in preventing resources from different origins, it also prevents legitimate interactions between a server and clients of a known and trusted origin.
Example: Cross-Origin (Different Origin) Forbidding communication.
Following image shows the communication between 2 different origins. Client browser is browsing the website at an origin, https://galodha.com and making ajax requests to https://anotherwebsite.com/api/users which is at origin `https://anotherwebsite.com. Since the client and server are at different Origins, the communication is forbidden.
Why do browsers enforce the Same-Origin Policy?
The same Origin policy was enforced in order to prevent security attacks like CSRF (Cross Request Forgery).
Example
If you are browsing your bank website and in another tab, while watching your favorite video, you have an advertisement, "You Won the lottery", and you are enticed by the ad and click the advertisement, and it opens another window. The advertisement is a malicious website and has ajax code to send the money from your account to a malicious site owner's account.
$.post('https://yourfavouriteBANK.com/transfer', { to: 'maliciousOwner', amount: '10000000' }),
Without further security measures, this would work because authentication cookies from yourfavouriteBANK.com would be sent and authenticate you. All the communication from one Origin to another is possible without restriction and can lead to the above attacks.
For more information, you may read here
CORS allows circumventing the same origin policy while not compromising on the security.
Let's break the CORS into smaller pieces to understand it better.
Cross-Origin
Cross-origin means that the origin of the request can be different from the domain that made the request. In Simple words, when a user browsing website X makes another request to website B, it is considered as Cross-Origin.
In our analogy, Origin refers to the Apartment Community. Two tenants from the same Apartment Community belong to the same Origin. However, your friends residing in another Apartment community are in a different Origin or Cross-Origin.
ResourceA resource is like an Image, font, Videos, Data etc. When we are making an Ajax call, we are requesting some data which is Resource as per the terminology.
In our analogy, the swimming pool was the resource. This is the valued possession which other people are interested to access.
Sharing
CORS defines various headers which allow the browser and server to communicate about which requests are (and are not) allowed and enable the resources to be shared.
In our analogy, the swimming pool was to be shared.
In our Analogy, we had a Secured Apartment Community which only allowed the tenants access to the resources. Imagine if the access to the apartment resources is not restricted, a malicious person can enter the Community and damage the swimming pool or other resources.
How does CORS allow us to bypass the Same-Origin Policy?
CORS specification provides a list of Headers values which browser and server communicate and understand to ensure that the different Origin can share resources.
In our Analogy, this was the Unique pass which allows your friends to inform Security that you have permission to enter into the Apartment community.
The Client sends requests to the server and the server responds by providing the information about the resources that are allowed and how the resources can be accessed. The Server sends the information in the Response Headers. Each Response header signifies an attribute for resource sharing. For example, the header Access-Control-Allow-Methods specifies the list of HTTP methods (GET, POST, DELETE etc.) which are allowed by the Server.
The following image shows how CORS response headers allow communication between 2 different Origins.
- Server: https://localhost:5001
- Client: https://localhost:44343
Due to the same origin policy, the AJAX request is blocked to a different Origin. However, we have enabled the CORS on the API at https://localhost:5001 by adding the response headers Access-Control-Allow-Origin: https://localhost:44343, which is allowing the API at https://localhost:5001 to receive any request from Origin https://localhost:44343
Most of the headers are prefixed with 'Access-Control-Allow'. A few examples are shown below.
- Access-Control-Allow-Origin
- Access-Control-Allow-Headers
- Access-Control-Allow-Methods
- Access-Control-Allow-Credentials
The most important of these is Access-Control-Allow-Origin, Let's explore this header.
Access-Control-Allow-Origin
The Access-Control-Allow-Origin header allows servers to specify a list of Origins with which the server will share the resources.
Referring back to our analogy, this basically allows specifying the list of friends which are allowed in your pool party. You are allowed to specify the list of the names that you would want to allow for the party. If your friend's name is not in the list, they will not be allowed to come inside.
During the development stage, the value can be set to *, meaning that Server will share the requested resources with any domain on the Internet. But please refrain from using this setting beyond local development.
Example
A browser client from Origin, https://galodha.com, wants to send a request to server https://api.github.com. Due to Same origin policy, the Client can't send the request to the server. However, when the Server responds with a response header Access-Control-Allow-Origin: https://galodha.com to the client, the Client Browsers allow making a request to Origin `https://api.github.
Pre-flight requests
Certain times, an additional Server Request is made by the browser before the actual request, which is also known as the Pre-Flight Request. Preflight requests use the OPTIONS header.
Why does the browser send an additional request in form of a pre-flight request? Isn't it an overhead?
The first time I read about the Pre-flight request, I couldn't make much sense of the concept, why do we need an additional Request to send before the actual Request. Isn't it an overhead of making an additional request?
The Pre-flight Request was added to the CORS Specification to allow communication with the Old Servers which don't understand CORS and safeguard against the potentially dangerous requests like Delete.
The following screenshot from the Chrome Developer tools shows the OPTIONS Request before the actual request is made. This is the pre-flight request.
Example: Why was the Pre-flight request added?
Let's go back in past, and assume a time when CORS Specification wasn't defined. Servers were not aware of the CORS Specification but did understand the Same-Origin Specification and allowed requests from the Same Origin Only.
An Origin like https://galodha.com is used by a server X for hosting blogs at https://galodha.com/blogs. This Server X knows about the same Origin policy and allows the operation like Delete a blog post from the same origin.
Now, CORS specification is launched. A new server Y is set up to manage projects at URL https://galodha.com/projects. The server Y supports the Get/Post/Delete operations on projects on the same origin.
The Projects are getting popular and other websites are interested to list the projects on their websites. So, you will need to allow for a Get Operation from the origin https://galodha.com. Also, there are open source contributors, so you need to offer the Delete operation as well from other websites/origin. Fortunately, the CORS specification has been launched and you know that by using the CORS header Access-Control-Allow-Origin: https://anotherwebsite.com, we can allow the requests from another website/origin. Also, using the CORS header, Access-Control-Allow-Methods: Get, POST, Delete, we can allow the GET/POST/DELETE operations from other websites.
So far so good, everything is going well and your server Y is getting a lot of traffic from other websites.
Next, a malicious user enters, and is using https://anotherwebsite.com and tries to perform a DELETE operation on the URL https://galodha.com/blogs on Server X. The origin https://galodha.com already allows requests from other websites for Server Y at https://galodha.com/Projects. Note that the same origin policy considers only the Scheme, HostName and port number, It doesn't consider the full path of the URL. So, a client can make a request to both https://galodha.com/blogs and https://galodha.com/projects as the browser thinks that both belong to the same origin.
Since Server X allowed Delete operations from the same Origin and it doesn't know anything about the new CORS specification, what should be the behavior for a DELETE operation on server X requested from another website/origin?
Should it allow you to delete a resource?
Wouldn't it be wonderful if server X can tell us that it doesn't support CORS? Can't we make an additional request to check if a Server supports CORS?
Yes, you are in luck, the CORS specification defines the Preflight Request which does the same things as we mentioned above. The PreFlight Request makes an additional request to ensure if the Server understands the request or not. If the server doesn't understand the request, then the client will not make the actual request. However, if the server understands the request, it will return the appropriate response mentioning what it allows and then the client can make the actual request.
What conditions Trigger a PreFlight Request?
A Pre-flight request is not made for all the Requests but is only applicable in certain cases. Following is the list of the conditions which govern if a pre-flight request is made or not.
- When the actual request is any HTTP Method other than GET, POST, or HEAD.
- If a POST request's content type is anything other than application/x-www-form-urlencoded, multipart/form-data, or text/plain.
- Also, if the request contains any custom headers, then a preflight request is required. Headers like Accept, Accept-Language, Content-Language etc doesn't trigger a Preflight request.
There are certain additional conditions which can trigger the Preflight request. For the complete list reference the link.
Conclusion
We gained a better understanding of the Origin (Scheme, Host and Port Number). We learned why browser enforces the same origin policy and how CORS specification allows the communication between different Origins. We looked at various CORS Headers which server needs to send to the client to allow the communication. I hope the analogy of the Apartment Community was helpful to understand the security concern and how the Security Pass helps us get around it. Lastly, we covered the Pre-Flight requests, which might be confusing the first time you see the request pop up in the network Toolbar.
I hope you have a better understanding of CORS now and can easily sneak your friends into your secure Apartment Community. All thanks to CORS. In the next article, we will take a look at the code that needs to be added at the Server side. Also, we will take a look at a simple JavaScript client which will communicate over a different Origin.
References
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- https://stackoverflow.com/questions/15381105/cors-what-is-the-motivation-behind-introducing-preflight-requests
- https://enable-cors.org/