CORS stands for Cross-Origin Resource Sharing and if you’ve built rich client applications that communicate with an API via REST, you have probably crossed paths with something known as same-origin policy. What this policy refers to is that an application cannot access resources via XMLHttpRequest or the Fetch API that come from a different URL than where the page was originally served from.
OK, so let’s get into specifics: first let’s see what exactly constitutes the same origin and what is a different origin. The policy looks at the protocol (http or https), port number and host URL. Let’s assume that our page is served from http://domain1.page.com/index.html. Accessing the following URLs will give the following results:
http://domain1.page.com/items/3213 # Success. https://domain1.page.com/items/12 # This fails because it's using https instead of http. http://somethingelse.com/ # Fails because it's a different domain. http://domain2.page.com/items/32 # Fails because the subdomain is different http://domain1.page.com:9000/items/32 # Fails because the port is different.
Of course, if you ever built applications using a microservice architecture, you know that sometimes it’s desirable to allow access to the backend for applications that will be hosted elsewhere, but you don’t necessarily want to open access to everybody. So CORS is sort of a middle road between the two and it comes in handy when your applications are distributed because sometimes you just want to hook up things without configuring proxies and whatnot.
We first must make the distinction between simple requests and preflighted requests.
A simple request is a GET, POST or HEAD that meets a few conditions:
- No event listeners are registered on any XMLHttpRequestUpload object used in the request and no ReadableStream object is used in the request.
- The only accepted headers are “CORS”: Accept, Accept-Language, Content-Language, Content-Type, Last-Event-ID, DPR, Downlink, Save-Data, Viewport-Width and Width.
- The accepted values for the Content header are: application/x-www-form-urlencoded, multipart/form-data and text/plain.
If the server supports CORS, it will return a response specifying a value for the Access-Control-Allow-Origin header:
Access-Control-Allow-Origin: * # this allows access from any origin Access-Control-Allow-Origin: http://domain2.page.com # allows access from domain2.page.com # etc.
A preflighted request is one that automatically triggers an OPTIONS call to the server before the actual request. It looks like this:
If any of the conditions for the request to be a simple request is violated, then we have a preflighted request on our hand. This means that if we want to support CORS, our server needs to be able to respond to OPTIONS requests accordingly.
This means we need something like:
HTTP/1.1 200 OK ... Access-Control-Allow-Origin: http://page.com Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: Content-Type
… to tell the browser, it’s OK to send the real request.
The whole concept of a “preflight request” seems a bit strange and you might be wondering why it was included in the spec. Couldn’t all requests be made without a preflight? A key concept to understand is that the role of the OPTIONS request doesn’t have to do with security.
Preflight requests have the role of preserving the semantics of the web before CORS.
Of course, you probably don’t want to be doing all that yourself, which is why there are libraries for supporting CORS in most platforms. If you’re using Node, it’s very simple to support CORS by using the cors library which is just simple Express middleware.
npm install cors --save
Using it is also very simple, it just needs to be registered as a middleware with Express.
Configuring Rails to use CORS is equally easy.