This post discusses the problem Of Cross-Origin Resource Sharing or CORS. CORS defines the principle that web pages should not access web services that are on a different domain to the source web page. An example of this would be to have a web page, index.html, which is running on site1.com. index.html sends a HTTP GET message to site2.com/webservice1. By default most modern browsers will not allow this for security reasons.
Making CORS-type calls is very handy though. It’s especially useful when you are working with AJAX and you are writing client jQuery pages that run on your home machine. These pages will want to make AJAX calls to web services that run on domains on the web and in this case by default the browser will cancel your request. If you open up Firebug in Firefox or the in-built developer tools in Chrome, you will find that your HTTP message fails for some reason or other. You’ll also notice that while you might have expected to see a HTTP GET or PUT message in the log, you will in fact be looking at a failed OPTIONS message.
So why is there no GET or PUT and why is the call failing?
First let’s consider the OPTIONS message. The sending of the OPTIONS message by jQuery AJAX is called a preflight sequence. jQuery sends out an OPTIONS message with a couple of parameters that see what the server is capable of. A typical OPTIONS message will include the following headers:
Accept:*/* Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3 Accept-Encoding:gzip,deflate,sdch Accept-Language:en-US,en;q=0.8,en-GB;q=0.6 Access-Control-Request-Headers:accept, origin, content-type Access-Control-Request-Method:GET Connection:keep-alive Host:site2.com Origin:null User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17
And the response will be something like this when the server does not do any special CORS handling:
Cache-Control:no-cache Content-Length:0 Date:Sat, 02 Feb 2013 17:42:59 GMT Expires:-1 Pragma:no-cache Server:Microsoft-IIS/8.0 X-AspNet-Version:4.0.30319 X-Powered-By:ASP.NET
And you’ll get no further than this. There will be subsequent GET or PUT message sent and the reason for this is that your browser observes that the requested host is different from the source of the call and it stops the conversation there.
So how do you get the browser to allow CORS calls? It’s simple. You just add the following 3 special CORS headers to the response packets for OPTIONS, GET, PUT and any other HTTP verb that you would like to have accessible from other hosts. Here’s a sample addition:
Access-Control-Allow-Headers:content-type Access-Control-Allow-Methods:PUT, GET, POST, DELETE, OPTIONS Access-Control-Allow-Origin:*
When your browser receives these headers alongside the usual headers, it will ignore the fact that the host and origin are different. Note that adding the headers above will allow connections from any origin. If you want to only allow connections from specific hosts, you can simply replace ‘*’ with a comma separated list of hosts as the value for the Access-Control-Allow-Origin header.
By adding in the three Access-Control headers, you will now see a conversation that looks like this. The browser sends an OPTIONS message and then a GET, PUT or whatever method you choose to communicate with your chosen web service and hopefully the service will return what you are looking for.
How you actually go about adding these HTTP headers on the server side depends completely on your server setup. If you use an Apache-PHP setup, you can quite easily get this up and running in a matter of a few lines of code. If you’re running an IIS-ASP setup, the solution can be more complex. I found that my Win2003 server, which ran IIS6, was returning a 403 status code for any OPTIONS messages that it received and I was unable to resolve this dead-end. I was, however, able to intercept OPTIONS and any other HTTP message coming into a web service that I installed on a Win Server 2012-IIS8-MVC4 setup.