CORS and IIS
Last time, I wrote about Cross-Origin Resource Sharing (CORS). This time I’ll show how it can be implemented with Microsoft’s Internet Information Services (IIS), ASP.Net and jQuery.
Let’s assume we have two very separate web applications:
http://cooking/
, a website that is full of delicous cooking recipeshttp://shopping/
, a website that allows users to make shopping lists
First, we implement http://shopping/
by adding 127.0.0.1 shopping
to the hostfile and creating an empty ASP.NET web application with only one file (list.aspx
):
<%@ Page Language="C#" ContentType="application/json" %>
[
{ "amount": "2", "name": "Apples" },
{ "amount": "4", "name": "Bananas" },
{ "amount": "8", "name": "Cookies" }
]
That was fast.
Now the guys from http://cooking/
would like to show shopping lists on their site and allow users to add ingredients for the recipe they’re looking at to their list. They add the following Javascript to their recipe-detail page to retrieve a user’s current shopping list:
Unfortunately this call is blocked due to the same origin policy. Luckily the girls from http://shopping/
want the whole world to use their shopping lists so they add an HTTP Header Access-Control-Allow-Origin: *
to their response. This can be done in the IIS Manager
- IIS6: in the Web’s properties dialog under ‘HTTP Headers’
- IIS7: in the ‘HTTP Response Headers’ section of the ‘Features View’, or web.config
Here is what the response from http://shopping/list.aspx
looks like after the change:
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Access-Control-Allow-Origin: *
Date: Sun, 17 Jul 2011 14:21:59 GMT
Content-Length: 124
[
{ "amount": "2", "name": "Apples" },
{ "amount": "4", "name": "Bananas" },
{ "amount": "8", "name": "Cookies" }
]
Now everybody, including the script on http://cooking
, is allowed to retrieve the shopping list.
Additionally it should be possible for users on http://cooking
to click a link ‘add to shopping list’ while they browse the ingredients for a recipe. The cooking-guys add the following Javascript to their site and have it run when users click on these links.
This will POST
the new item (16 Dark Chocolates) to the list but since it is a different domain, the browser will first query Access-Control
with an OPTIONS
request:
OPTIONS http://shopping/list.aspx HTTP/1.1
Host: shopping
Origin: http://cooking
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
In particular it checks for the permission to POST
with a header content-type
. Fortunately the http://shopping
site responds with
HTTP/1.1 200 OK
Allow: OPTIONS, TRACE, GET, HEAD, POST
Server: Microsoft-IIS/7.5
Public: OPTIONS, TRACE, GET, HEAD, POST
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: Content-Type
Date: Sun, 17 Jul 2011 17:49:22 GMT
Content-Length: 0
so the browser goes ahead with the original POST
:
POST http://shopping/list.aspx HTTP/1.1
Host: shopping
Content-Type: json; charset=UTF-8
Referer: http://cooking/
Content-Length: 38
Origin: http://cooking
{"amount":16,"name":"Dark Chocolates"}
It is possible that everything already works for you after configuring the two additional Access-Control-
headers (-Allow-Methods
and -Allow-Headers
). IIS7 has an OPTIONSVerbHandler
configured by default that will respond with all configured custom headers, including the Access-Control-
headers. In IIS6, there is no such mapping and depending on your setup it is likely that you need to explicitly allow the OPTIONS
verb. In IIS6 this setting is certainly not obvious but you can find it in the website’s Properties -> Home Directory -> Configuration… -> Mappings -> .aspx -> Verbs -> Limit to. If you’re using a different handler (e.g. WCF) you’ll have to add it to the appropriate mapping (e.g. ‘.svc’). This will allow your ASP.Net application to receive OPTIONS
requests. One way to handle these requests is to simply end the response in the Global.asax
’s Application_BeginRequest
method, optionally also setting the Access-Control-
headers:
Some other things to look out for when it doesn’t work:
- Don’t just rely on your browser plugins (Firebug etc.) to find out what happened. Use a debugging proxy (e.g. Fiddler, Charles, etc.) or network tools (e.g. Wireshark). There is a really fine line between request-blocked-by-browser (i.e. no request actually happened),
OPTIONS
request problems andGET
orPOST
problems. The plugins’ error messages are not always helpful (e.g. ‘405 Method not allowed’) - Depending on your ajax-call setup and content type (json, xml, etc.) you might have to add the
Accept
header to the list of-Allow-Headers
. - If there is a problem with the
OPTIONS
request, check the<system.webServer><handlers>
section, there may be handlers with verb limits configured (IIS7 only) - If there are still problems with the
OPTIONS
request, check the<system.web><httpHandlers>
section for custom handlers - Don’t forget about any firewall or security tools that are filtering requests with specific (or unknown) verbs or headers (e.g. UrlScan)
Here is the full sample code: cors-and-iis.zip