Cross-origin resource sharing, or CORS, is a mechanism that allows AJAX requests to circumvent their same origin limits.
For demonstration purposes, we’ll use a small Ruby project called F1 race results. It presents a page with the results of the current F1 Grand Prix in real time. The user clicks on a button to refresh the race standings while the page is kept on screen.
Download the project,
bundle install and launch it
NOTE: Due to Chrome issues, please use the Firefox browser to test the exercises.
A bit of history
The ability of a browser to request a resource from a server without reloading the page hasn’t been available since day one.
If we point our browser to http://localhost:3000/iframe we’ll see a list of results. As intended, clicking in the button makes the page refresh the list of results but avoiding a full page reload.
How is it done? The JS code in the page detects the click in the button and changes the
attribute of a hidden
iframe tag. This
src change is detected by the browser, which will
request the resource asynchronously. The requested resource is in fact a small page that contains
a little JS snippet responsible of changing the race results in the main page.
The drawback with this approach is that you can’t make POST requests and it is a bit hacky.
To avoid this hackyness (back in 1999) Microsoft added XMLHttpRequest as a JavaScipt extension to Internet Explorer 5. With XMLHttpRequest there’s no need to use an
iframe to do background requests.
It remained quite as a propietary rarity until the early 2000s, when several webapps started using it.
Shortly afterwards, somehow the technique was named as AJAX. The name is rather misleading, as it allows not only the page to request a resource asynchronously, but synchronously too. Even more, it’s not restricted to XML, because JSON or plain HTML can be sent as well. In those cases, the technique is often called AJAJ and AHAH respectively. However, nobody is so specific, and the name AJAX is widely accepted.
The XMLHttpRequest is conveniently wrapped in jQuery by
Back to our example app, in http://localhost:3000/ajax clicking the button will reload the results. Exactly like the previous example, just using this new technique.
However, AJAX is an open door for insecurity. Your page may contain a third party script that silently sends your session cookie to another server.
Same origin policy
To prevent security risks, the browser enforces a same origin policy.
What can be considered a different origin?
- when the domain is different
- when the protocol is different
- when the port is different
To see this policy in action, let’s edit the file
app/views/ajax/index.html.erb and replace the line:
and reload the page. Now clicking the button won’t refresh the standings. The browser’s network/console panes will be showing an error. Why? It’s preventing the request because the origin is different: although the domain remains unmodified, both protocol and port have changed.
A possible workaround for this is JSON with padding, known as JSONP.
It’s a similar technique as the iframe’s, but using a script tag. script tags are not bound to same origin as iframe tags are.
For convenience purposes, the script tag creation is abstracted by jQuery’s
and we might want to replace our code with:
Nonetheless, this looks as hacky as the iframe technique, and doesn’t support any other HTTP method than GET. It can’t be set synchronous as AJAX can, and it forces us to program the callback function. Furthermore, it may open a big security risk as third party servers might include insecure or untrusted code.
What is the differences between this and 1996’s
iframe technique then? Simply put, an iframe can’t modify the code of the parent page if it comes from a different domain, but a script can call the parent page.
Finally, to overcome all these difficulties, the Cross-origin resource sharing specification was born. It’s still a working draft, but widely accepted.
CORS-aware browsers, instead of preventing the cross origin AJAX straight away, send an
Origin: domain.tld header to the server. In our case this header looks like:
The browser will see if server’s response has an
header. If so, and the domain specified in the header matches the domain that originated the
request, the response will be accepted. In our case, the response header is:
The header can be easily set in a Rails controller with just:
You can see all this in action going to http://localhost/cors. Show the network pane in your browser and click the refresh button in the page.
Unlike previous examples, in this case we are performing a cross origin POST request, instead of GET. How cool is that?
Fine grain control
The theory behind the headers is initially simple. But the server’s response can be tuned for
certain requests. If we want to restrict GET requests, the server should have this header:
Check the whole list on http://www.w3.org/TR/access-control/#resource-requests.
CORS requests are sent straight to the server, unless:
- HTTP method is not simple, i.e. other than:
- Content-Type is not simple, i.e. other than:
- request has authentication headers
…among others. Check the full list of conditions.
In any of these scenarios, the browser will do first a preflight request. This is simply
a request using the
OPTIONS HTTP verb. If the request succeedes, the browser will issue the actual request right afterwards.
This preflight request is cached by the browser so the server is not bothered more than
A very common case is a
POST application/json request. It is very advisable to test if your
server is accepting preflight requests from the command line.
If our server requires authentication, the AJAX/CORS call has to propagate the credentials. Basic auth credentials are part of the request headers and will fall under the cases when the browser has to do a preflight request.
Simply add the
xhrFields parameter to include auth credentials. Example:
CORS and Rails
Setting all these headers can be cumbersome, if we have to do it manually in every controller
or with a before_filter. It’s even worse if our requests need a preflight response, because we
would have to set up
Wouldn’t be nice to have a middleware that does the heavy lifting for us? Granted: Rack::Cors.
Have a look at
Remove this block, restart the server and try again http://localhost/cors. It won’t work.
'*' cannot be used to define origins if we need to support credentials.
It is worth noting that you have to disable
protect_from_forgery (CSRF protection) when
doing POST requests because we’re dealing with different servers that don’t share sessions.
CORS and a cloud storage facility
There’s documentation for Google as well.
CORS current limitations
CORS is available in Chrome, Opera 12, Safari 4, Firefox 3.5 and Internet Explorer 10. Support in mobile browsers varies, although works fine in latest iOS and Android.
Chrome works fine in the vast majority of scenarios.
You may encounter difficulties when the request is targeting a HTTPS server using
self signed certificates.
A possible workaround would be add the certificate
in your trusted list. The certificate that comes with the
thin webserver will fall under this category.
Another problem would be if you use basic auth and HTTPS. Chrome needs you do a manual request to any resource protected by HTTPS and basic auth to make the CORS request work.
Internet Explorer: a classic contender in any “limitations” section
- do any other request than GET or POST
- add authentication headers
- request nothing but
- target a different protocol
It’s better to stay away from CORS and IE 8/9. In these situations, you may want to implement any of the techniques previously described, as a fallback.
IE 10 has full support though, out of the box, via the standard XMLHttpRequest.
We’ve depicted how to make background requests in several ways, from the fundamentals to the latest standards.
Here at HouseTrip we’ve decided that the CORS technology is mature enough and we’re using it successfully for some of our sign in and sign up forms, securely sending requests to the server.
I really hope this exploration process has helped you to understand how AJAX & CORS work, and reduces the trouble you’ll probably have setting it up in your next project.
This post was written by me and first appeared on HouseTrip’s dev blog.