Cross-Origin Resource Sharing (CORS) is something I keep running headlong into and is always notoriously difficult to debug. So I decided to dig deep into CORS and get a better understanding of what it is, why it works the way it does, and how to best work with it.

To understand CORS we first need to look at web browsers and how they communicate with web services. An HTTP request is sent either through a user interaction to the browser, or a script that runs on the page. When the request is made, the browser will check it’s local storage for any cookies and send those along with the request.

This allows a user to authenticate to a site like bank.com and stay logged in while browsing different pages. It’s the core of how authentication works on the web, but it does have some issues.

Example:

A user opens fakebank.com and the page has scripts that makes requests to bank.com/api/accountdetails the browser will pass along the authentication cookies along with the request, and the results is that the web service will see that the request is authenticated and respond with the information. Allowing the “fakebank” web site to load the account details of the user.

To prevent this security issue browsers have implemented a feature called Same-Origin Policy (SOP), which looks at the domain the user is currently viewing (the origin) and makes sure it is the same domain that the request is made to. Request are always stopped by the browser unless there is a specific exception made.

One thing to note about SOP is that it’s primarily directed at running scripts and fonts, normal requests like loading images, style sheets (CSS), and even loading scripts is not impacted. This allows loading images and scripts from CDN or a different domain than the main domain of the web site. E.g. bank.com loads images and scripts from static.bank.com

CORS - Exceptions to SOP

In some cases you want to support using different domains for different parts of your site. bank.com might have an api at api.bank.com, but since this is a different domain, SOP will prevent requests from a script in bank.com to be sent through.

To enable this, api service on api.bank.com can tell the browser which other domains are allowed to make requests to the service. This is in essence what CORS does, it provides the browser with a list of domains (origins) that should be allowed to make requests, it can also define in more details what type of requests can be made.

The browser will send a “CORS preflight” request to the service before the real request is sent. This allows the browser to verify with the server if the request should be allowed through. This is an OPTIONS request to the exact same URI that the request will be sent to.

The service will respond with a status code of 20* and a set of headers that tells the browser what the server allows.

1
2
Access-Control-Allow-Origin: https://bank.com
Access-Control-Allow-Methods: POST, GET, OPTIONS

If the domain that the browser makes the request from (origin) is listed in the Access-Control-Allow-Origin header and the method (e.g.GET) is listed in the Access-Control-Allow-Methods header to the preflight request, it will continue and send the real request.

There are a few other headers that can be sent back for a CORS request. For a detailed breakdown of these and their purpose please check out the mozilla developer page on CORS.

From a high level this is the core of SOP and CORS, it’s a relatively simple consept that is there to protect users from unwittingly expose details across different web services. The default is to now allow any cross origin requests so to avoid the complications of CORS and SOP, don’t run your API under a different domain than your web service. It makes it much simpler, and CORS does not impact you or your users.