The team spent much of the week working through this issues related to Chrome running on Windows 8 consuming cross-origin resources using Web API. We thought it was resolved on day 2 but it resurfaced the next day. We definitely resolved it today though. I believe I do not fully understand the situation but I am going to explain what I know in an effort to help you avoid and/or resolve a similar issue.

Background

As a measure of protection, Web designers (W3C) and implementers (Google, Microsoft, Mozilla) made it so that a request, especially a JSON request (but really any URL), sent from one domain to another will only work if the requestee “knows” about the requester and allows requests from it. So, for example, if you write a ASP.NET MVC Web API service and try to consume it from multiple apps, the browsers used may (will?) indicate that you are not allowed by showing an “Access-Control-Allow-Origin” error indicating the requester is not allowed to make requests.

Internet Explorer (big surprise) is the odd-hair-colored step-child in this mix. It seems that running locally at least IE allows this for development purposes. Chrome and Firefox do not. In fact, Chrome is quite restrictive. Notice the images below. IE shows data (a tabular view with one row for each day of a week) while Chrome does not (trust me, neither does Firefox). Further, the Chrome developer console shows an XmlHttpRequest (XHR) error.

Screen captures from IE (left) and Chrome (right). Note that Chrome does not display data and the console shows an XHR error.

Why does this happen?

The Web browser submits these requests and processes the responses and each browser is different. Okay, so, IE is probably the only one that’s truly different. However, Chrome has a specific process of performing a “pre-flight” check to make sure the service can respond to an “Access-Control-Allow-Origin” or Cross-Origin Resource Sharing (CORS) request. So basically, the sequence is, if I understand correctly:

This situation occurs for both GET and POST methods. Typically, GET methods are called with query string parameters so there is no data posted. Instead, the requesting domain needs to be permitted to request data but generally nothing more is required. POSTs on the other hand send form data. Therefore, more configuration is required (you’ll see the configuration below). AJAX requests are not friendly with this (POSTs) either because they don’t post in a form.

How to fix it.

The team went through many iterations of self-hair removal and we think we finally have a working solution. The trial-and-error approach eventually worked and we referenced many sources for the information. I indicate those references above. There are basically three (3) tasks needed to make this work.

Assumptions: You are using Visual Studio, Web API, JavaScript, and have Cross-Origin Resource Sharing, and several browsers.

1. Configure the client

Joel Cochran centralized our “cors-oriented” JavaScript (from here). There are two calls including one for GET and one for POST

There are actually two steps here. Do you remember above when we mentioned the “pre-flight” check? Chrome actually asks the server if it is allowed to ask it for cross-origin resource sharing access. So you need to let the server know it’s okay. This is a two-part activity. a) Add the appropriate response header Access-Control-Allow-Origin, and b) permit the API functions to respond to various methods including GET, POST, and OPTIONS. OPTIONS is the method that Chrome and other browsers use to ask the server if it can ask about permissions. Here is an example of a Web API controller thus decorated:

NOTE: You’ll see a lot of references to using “*” in the header value. For security reasons, Chrome does NOT recognize this is valid.

[HttpPost] [HttpOptions] public void UpdateFooItem(FooItem fooItem) { // NOTE: The fooItem object may or may not // (probably NOT) be set with actual data. // If not, you need to extract the data from // the posted form manually.

* The header attributes at the class level are required. Note all of those methods and headers need to be specified but we find it works this way so we aren’t touching it.

* Web API will actually deserialize the posted data into the object parameter of the called method on occasion but so far we don’t know why it does and doesn’t.

* [HttpOptions] is, again, required for the pre-flight check.

* The “Access-Control-Allow-Origin” response header should NOT NOT NOT contain an ‘*’.

3. Headers and Methods and Such

We had most of this code in place but found that Chrome and Firefox still did not render the data. Interestingly enough, Fiddler showed that the GET calls succeeded and the JSON data is returned properly. We learned that among the headers set at the class level, we needed to add “ACCEPT”. Note that I accidentally added it to methods and to headers. Adding it to methods worked but I don’t know why. We added it to headers also for good measure.

Next Steps

That should do it. If it doesn’t let us know. What to do next?

* Don’t hardcode the allowed domains. Note that port numbers and other domain name specifics will cause problems and must be specified. If this changes do you really want to deploy updated software? Consider Miguel Figueira’s approach in the following link to writing a custom HttpHeaderAttribute class that allows you to specify the domain names and then you can do it dynamically. There are, of course, other ways to do it dynamically but this is a clean approach.

I saw that come up several times. I unfairly avoided it because a) learning curve and b) figured there must be a "quick" solution. The more we dug into it the worse it got. The thing that killed us was the part where we missed the "ACCEPT" header. I will take a look at your implementation however. If for no other reason than my own edification but hopefully to use it.
11/2/2012 9:21 PM | Brian C. Lanham

Thank you for such an awesome CORS implementation. I had problems with Thinktecture.IdentityModel because I am running in IIS 6 but your implementation is working in firefox and chrome. In IE, there is a javascript error in: xdr.send(data); lineError: Invalid argument.

why do you think xdr.send(data) is not working in IE but working in Chrome and Firefox.

Sanjiv, I will ask Joel to help me look into this and see if we can identify something that may help you. Is your "fakeData" in JSON format? Are you using Fiddler to inspect your transmissions? If not you really need to get Fiddler and see the details behind the calls.
1/17/2013 8:30 AM | Brian C. Lanham

Thank you for your quick response. I am using Fiddlers to do inspection. It is an awesome tools to have. My "fakedata" is in JSON format. That's one of the reason it works in Chrome and Firefox. I still havenot figured out why it is not working in IE..to be specific IE 10.
1/17/2013 12:16 PM | Sanjiv Lamsal

I ran into this issue and quite frankly, I cheated. I created a WCF service endpoint with webHttpBinding. The client (IE, FF, Chrome), fires an AJAX POST request to a LOCAL service endpoint. I take the stringifyed parameter info to the web method and carry it into my biz logic. From there I issue a Restsharp (104.1 net) request: RestRequest request = new RestRequest();request.AddHeader(ANYHTING I WANT - SECURITY STUFF FOR EXAMPLE);

I control everything at this point - in terms of the request AND the response. With the request, I execute as a GET, POST, PUT, DELETE. With the response, I do some deserialization, JSON data cleanup, parsing - whatever - and return to service endpoint and ultimately to the client with data to markup.

This technique works in IE, FF, Chrome.

I have to admit, it was a real head-scratcher and does not even come close to the elegant work around that I read in this blog.

Have you been able to get this to work when the origin url is different than the webapi url? E.g. origin (app.foo.com) and webapi (api.foo.com or api.bar.com)? I can only get this to work if both are the same but can have different ports.

Also, in either case I can't get FF to pass credentials, i.e. cookies - is this even possible?

Nothing wrong with "cheating". The important point is to get it to work. Afterwards you can deal with specific implementation details. Your solution is definitely interesting
3/25/2013 7:18 PM | Brian Lanham