Related: The JavaScript Same-Origin Policy

Web security can be a royal pain sometimes, and as developers it's all too easy to see it as an impediment to meeting a ship deadline, at least until an attacker finds a way to infiltrate our carefully crafted, beautiful creations. Then it becomes our first priority. Not coincidentally, it's too late.

I recently had a run-in with cross-site scripting issue, specifically the ever-delightful same-origin policy. According to Mozilla Developer Network (MDN), this is the policy that "restricts how a document or script loaded from one origin can interact with a resource from another origin." Basically, it means that a web page loaded from one domain can't make Ajax requests to another domain. Although it's not quite that simple, this description will do for our needs here. On one hand, this restriction is a good thing, because it prevents a whole class of nasty attacks. But on the other hand, it's also nasty because it prevents some things that should be easy and natural on the web. Things such as mashups--combining data from various sources into a single dashboard-like page--are hard under the same-origin policy, and it even complicates application deployment, which was the problem for me in a recent project.

Related: ASP.NET Web API: A REST perspective

The application was a very simple single page that let users search some historical information and displays the results with lots of external links if they want to dig deeper. No, it wasn't one of those fancy single page applications that are all the rage these days, but literally a single HTML page that allows some simple search parameters and displays some results and the details for each hit. A perfect situation for client-side code hitting a few ASP.NET Web APIs.

And this solution is easy to develop. Fire up Visual Studio, create a Web API 2 project, add a test HTML page, and code the controller using Entity Framework to hit the database. Then write some easy client-side code to make some Ajax calls and handle the results. It didn't even need much security: the API was read only access to the data, and we could care less if someone else built their own page to access the API. We wanted the information to get out there to anyone interested. And if you weren't part of a family with ties to Alaska going back decades, you weren't interested, except maybe to satisfy some itchy curiosity.

I coded it all up, passed it on to the web designer to make it beautiful, tested the heck out of it locally, and deployed it for testing. But, dang it! It didn't work. Yep, you guessed it: the web page went to one server and the API got deployed not just to another server but another domain. The difference was only the top-level domain (TLD), but that's enough to trigger same-origin policy issues. Can't believe I didn't see that coming.

Thankfully, the W3C's Cross-Origin Resource Sharing (CORS) standard has started making its way around the web with relatively modern browsers supporting it. CORS is officially a completed W3C Recommendation since January 16, 2014. Most browser vendors didn't wait for the completed specification, so CORS is pretty widely available.

What CORS does is let a web server relax the same-origin policy in very controlled ways, letting other sites call an API via HTTP. CORS is implemented through a series of HTTP request and response headers in which the browser asks permission to make the cross-domain request, and the server says yea or nay. ASP.NET Web API 2 implements CORS, and the MSDN article, "CORS Support in ASP.NET Web API 2" by Brock Allen gives a nice summary of the HTTP conversation that happens as well as how to implement it in a controller. See "Enabling Cross-Origin Requests in ASP.NET Web API" by Microsoft's Mike Wasson for a detailed tutorial of using CORS in ASP.NET.

For the simplest use cases, there are just a few steps to using CORS in a Web API project. First, you need to install the CORS package, which you can install using the following NuGet command:

Install-Package Microsoft.AspNet.WebApi.Cors

You've just gotta love NuGet! The package installs the System.Web.Cors.dll and System.Web.Http.Cors.dll assemblies.

Second, you need to enable CORS in the application. The following line in the Register method of the WebApiConfig file does the trick:


Third, you need to decorate your Web API methods with the EnableCors attribute. You can do this for individual methods, at the Controller class level, or globally in the application. The attribute has four parameters:

  • List of origins allowed

  • List of request headers allowed

  • List of HTTP methods allowed

  • List of response headers allowed (optional)

Each of these can contain a comma-delimited list of multiple options or * to allow any and all. The attribute’s constructor is overloaded to allow either three or four parameters, and you can also use the SupportsCredentials and PreflightMaxAge properties as well.

In my highly liberal historical application, I added the following EnableCors attribute to my controller:

[EnableCors(origins: "*", headers: "*", methods: "*")]

Alternatively, you can also write it like this:

[EnableCors("*", "*", "*")]

This line of code tells Web API to allow cross-domain requests from any and all domains, allow any and all applicable request headers, and allow any and all HTTP methods. In a more restrictive application, you're likely to specify specific origins and limit the methods to something like PUT and POST. A more realistic attribute might look something like this:

[EnableCors(origins: ",", "*", "PUT,POST")]

There's a lot of flexibility in how you apply the EnableCors attribute. You can limit its use to specified HTTP methods at the application or class level, but apply different settings to a method or two. You can also use it at a wider scope, but disable it for one or more individual methods or controllers using the DisableCors attribute, which doesn't require any parameters. The CORS support for ASP.NET Web API article on the Web API CodePlex wiki has a pretty good description of the options.

Fourth and finally, you might need to craft your Ajax requests a little differently. There are a lot of options here, particularly if you mess around with sending various HTTP headers as part of the request. And how you do that depends a little on any client-side JavaScript frameworks you use. With jQuery, I've not found any need to modify my Ajax method calls at all unless deeper security is needed. You might be tempted to use the crossDomain setting for the method, but that just "force[s] a crossDomain request (such as JSONP) on the same domain." If you're making a cross-domain request anyway, then it doesn't do a thing.

That said, you might certaintly need to implement Web API security and do things such as provide security credentials through your API calls. But that's a separate issue from CORS, so not overly relevant here.

As usual, Internet Explorer (IE) requires a footnote in any discussion about browser behavior. The good news is that IE added full CORS support in IE 10. But what about earlier versions? Fortunately, someone wrote a jQuery extension that enables CORS in IE 8 and 9, and I've found that it works great. All you have to do is include the jquery.xdomainrequest.min.js script file on your site, and use the following code to implement it:

<!--[if lte IE 9]>
 <script src="/Scripts/jquery.xdomainrequest.min.js"></script>

Okay, okay, this is a bit messy. The xdomainrequest extension purportedly only works for IE 8 and 9, but no one uses IE 7 or earlier anymore. Do they? Sigh, yes, I know. But I hope for all our sakes it's a very small portion of your web visitors. I've found that the xdomainrequest solution works perfectly as a drop-in solution. Assuming you download the JavaScript code and not the download page's HTML code, as is all too easy to do with GitHub!

CORS is a great technology for enabling cross-site Ajax requests, and ASP.NET Web API 2 implements it well. It makes it possible to build pages with mashups of data from a several different websites and do a lot of other creative things in your web application.