Using CORS to access ASP.NET services across domains
AJAX, ASP.NET, jQuery By Dave Ward. Updated May 9, 2013Note: This post is part of a long-running series of posts covering the union of jQuery and ASP.NET: jQuery for the ASP.NET Developer.
Topics in this series range all the way from using jQuery to enhance UpdatePanels to using jQuery up to completely manage rendering and interaction in the browser with ASP.NET only acting as a backend API. If the post you're viewing now is something that interests you, be sure to check out the rest of the posts in this series.

Work on client-side applications long enough and it’s just about inevitable that you’ll eventually want to make an AJAX request that breaches the browser’s XMLHttpRequest security restrictions. Limitations on cross-domain requests are great when they’re preventing malicious sites from malfeasing, but are a thorn in the side when they complicate your legitimate applications.
Traditionally, direct communication across the same-origin boundary required using a rickety (though clever) workaround called JSONP. JSONP is a reasonable compromise if all you need to do is make blind requests to a third-party API like Twitter, but comes up short if you need to use any HTTP verb other than GET. Of course, that’s a deal-breaking issue when you’re working with ASMX ScriptServices or ASPX page methods.
Luckily, a relatively new feature has been making its way into browsers which provides a robust solution to the cross-domain AJAX problem: CORS.
In this post, I’m going to show you how to recognize exactly which requests are cross-origin, how to enable CORS for your ASP.NET site, and the extra configuration necessary when you’re working with ASP.NET’s JSON-enabled services.
Before we get started, I want to emphasize that this approach won’t work with any version of IE prior to IE10. If supporting older versions of IE is a requirement in your target environment, you’re stuck with something like JSONP or a server-side proxy. This will work in any version of IE if Chrome Frame is installed and enabled by your site/server though.
Understanding what constitutes a different “origin”
When it comes to making cross-origin AJAX requests, it’s helpful to understand which requests actually are cross-origin. Of course, a request from a page served under one domain to a resource on an entirely different one definitely falls under the umbrella of cross-origin:

However, the common moniker for these requests, cross-domain, tends to be misleading. While a request from one domain to another is obviously cross-origin, browsers are much more picky than that.
For example, a request from foo.domain.com to bar.domain.com is just as much cross-origin request as one to abc.com. That’s not quite as intuitive as the traditional cross-domain scenario, but requires the same consideration.
A less obvious troublemaker is making a request from localhost:8080 to localhost:8081, which XMLHttpRequest’s security restrictions will also deny you from making. That’s a particularly troublesome scenario for ASP.NET developers since Visual Studio is all too happy to help you spin up a separate services/API project almost transparently hosted at its own separate port.
The easiest way to determine whether a particular request will fall under the umbrella of cross origin resource sharing is to compare the portion of both addresses appearing before the first forward slash. Any difference before the first forward slash, including the port, protocol (HTTP vs. HTTPS), and sub-domain, will thwart a traditional XHR-based AJAX request.
Enabling CORS for an ASP.NET site
Though the conventional wisdom about cross-domain or cross-origin requests has long been JSONP or nothing, a solution called Cross-Origin Resource Sharing has gained broad support in modern browsers over the past couple years. This standard defines a set of HTTP headers that a server can use to instruct browsers that it’s okay for certain XMLHttpRequest requests to that server to cross the same-origin boundary.
In the broadest case, enabling CORS for an ASP.NET site is simple. The enable-cors.org site has handy list of concise examples of how to configure a variety of servers for CORS, including IIS. For sites hosted on IIS7.0+ and Integrated Pipeline, enabling CORS is as simple as adding this to the site’s web.config:
<system.webServer> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="*" /> </customHeaders> </httpProtocol> </system.webServer>
That simple change gets you half way there. A regular GET request for an ASPX page’s HTML would work now, for example. Unfortunately, the Content-Type necessary for working with ASMX ScriptServices throws a new wrench in the works:

As it turns out, requests to ASP.NET’s JSON-enabled endpoints require one last configuration step to allow the request to go through unimpeded.
Access-Control-Allow-Headers
Sending the Access-Control-Allow-Origin header allows basic cross-origin access, but calling ASP.NET services like ASMX ScriptServices, ASPX page methods, and WCF services with AspNetCompatibilityRequirements enabled requires jumping through a few more hoops. Namely, sending a specific Content-Type header of application/json.
One of the security precautions that the CORS specification includes is that browsers must tightly control what’s sent along with these cross-origin requests. Among the items that must be explicitly allowed: any but the most basic HTTP headers, including Content-Type.
In order for CORS requests to specify their Content-Type, your site needs to respond with one additional CORS header: Access-Control-Allow-Headers
That brings the web.config modifications necessary to this:
<system.webServer> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Headers" value="Content-Type" /> </customHeaders> </httpProtocol> </system.webServer>
And finally, it’s possible to use the same, familiar jQuery $.ajax() syntax to request our service from same- and cross-domain origins alike:

Conclusion
Even as we’re teetering at the precipice of 2012, CORS is still a fairly new feature and lacks ubiquitous support. IE10 should support CORS as fully as Chrome and Firefox already do, but the XDomainRequest implementation in earlier versions of IE does not (to my knowledge) support Access-Control-Allow-Headers, which makes it all but useless in this scenario.
If you’re comfortable leaving the comfy confines of XMLHttpRequest and jQuery’s $.ajax, a more cross-browser friendly solution does exist: easyXDM. I haven’t personally used easyXDM beyond a bit of experimentation, but the approach it takes in older browsers is one that has been proven to be solid by similar libraries like Socket.IO.
However, if you’re working in an environment where you can mandate Chrome (or Chrome Frame in IE9-) and/or Firefox as the browser of choice, CORS is an invaluable addition to your bag of tricks (I feel like I’ve typed that phrase in another post recently…). I hope this post will help you get your feet wet with this new technology which seems to get more lip service than actual usage.
Get the source
If you’d like to browse through a complete working example of what’s been covered in this post, take a look at the companion project at GitHub. Or, if you’d like to download the entire project and run it in Visual Studio to see it in action yourself, grab the ZIP archive.
Browse on GitHubDownload the ZIP
Similar posts
What do you think?
I appreciate all of your comments, but please try to stay on topic. If you have a question unrelated to this post, I recommend posting on the ASP.NET forums or Stack Overflow instead.
If you're replying to another comment, use the threading feature by clicking "Reply to this comment" before submitting your own.



“Work on client-side applications long enough and it’s just about inevitable that you’ll eventually want to make an AJAX request that breaches the browser’s XMLHttpRequest security restrictions” – long enough here can mean even a day or two!
nice, always innovating.
Hi Dave,
Thanks for the tip about easy XDM – I’ll definitely look into it!
However, I’d like to ask you about your opinion on something I’ve ran into recently: pr-flight requests:
http://www.w3.org/TR/cors/#preflight-request
https://developer.mozilla.org/En/HTTP_access_control
I’m poking at it with ASMX and WCF, but I was wondering if you have any tips on how to handle such requests.
(I think that this is relevant to this article seeing as how if you want to use a “write” verb such as POST or PUT to a cross-domain web resource, you run into preflight requests)
jQuery handles the preflight automatically when it detects that the
$.ajax()request is a cross-origin one. The single$.ajax()call shown in the screenshots above actually generates two requests. First, an OPTIONS request to check the CORS headers and then a POST after the necessary CORS headers have been verified.After adding the Access Control allow headers section to my web config file (.Net 4 under IIS 7) and restarting the site, I get the following error: “The configuration section ‘system.webserver’ cannot be read because it is missing a section declaration”.
Searches online have not given me any relief. Any idea what might cause this error?
If I remove the system.webserver from the configuration section and restarting the site, everything is back to normal.
Thanks for your help,
Lonnie
UPDATE: This issue resolved itself after I went to IIS Manager and selected the web site, then opened Handler Mappings.
From there, I selected “WebServiceHandlerFactory-ISAPI-4.0_32bit” , then clicked “”Request Restrictions”.
From there, clicked the Verbs tab and added “Options” to the list of handled verbs.
I clicked OK and Yes when prompted to allow the changes.
I repeated these steps for “WebServiceHandlerFactory-ISAPI-4.0_64bit”.
When I checked the web config file, it had the following changes:
<system.webServer> <handlers> <remove name="WebServiceHandlerFactory-ISAPI-4.0_32bit" /> <remove name="WebServiceHandlerFactory-ISAPI-4.0_64bit" /> <add name="WebServiceHandlerFactory-ISAPI-4.0_64bit" path="*.asmx" verb="GET,HEAD,POST,DEBUG,OPTIONS" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" resourceType="Unspecified" requireAccess="Script" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" /> <add name="WebServiceHandlerFactory-ISAPI-4.0_32bit" path="*.asmx" verb="GET,HEAD,POST,DEBUG,OPTIONS" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" resourceType="Unspecified" requireAccess="Script" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" /> </handlers> </system.webServer>I then added the httpProtocol sections from your instructions to the system.webServer per your instructions above, so entire section is now:
<system.webServer> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Headers" value="Content-Type" /> </customHeaders> </httpProtocol> <handlers> <remove name="WebServiceHandlerFactory-ISAPI-4.0_32bit" /> <remove name="WebServiceHandlerFactory-ISAPI-4.0_64bit" /> <add name="WebServiceHandlerFactory-ISAPI-4.0_64bit" path="*.asmx" verb="GET,HEAD,POST,DEBUG,OPTIONS" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" resourceType="Unspecified" requireAccess="Script" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" /> <add name="WebServiceHandlerFactory-ISAPI-4.0_32bit" path="*.asmx" verb="GET,HEAD,POST,DEBUG,OPTIONS" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" resourceType="Unspecified" requireAccess="Script" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" /> </handlers> </system.webServer>(Notice case changes to httpProtocol and customHeaders per VS 2010)
After restarting the web site, I no longer got the Internal Server error, although my remote request still doesn’t work in either Chrome or FireFox (status is now shown as “Load Cancelled” in Chrome).
Issue still not resolved, but may be on the right track.
If it helps, this is the full web.config from the project I’m making requests to in this post’s examples: https://github.com/Encosia/Encosia-Samples-ASMX-CORS/blob/master/Web.config
Thanks Dave, I looked at that earlier and have included it in my own. Still not sure why my server errored when I copied and pasted the block manually, but once I made the changes through IIS Manager it was fine.
I still have not been able to get the cross domain issues resolved. My code works perfectly in IE from my client PC but fails in all other browsers (Chrome, Firefox, Opera and Safari).
I actually have 2 different solutions with the same cross domain issue. One is an iframe loading an aspx page from my server into the clients browser. This works until I try to get a value from the iframe back into the client’s (iframe’s) parent page.
The other is a jquery/ajax function calling a SOAP web service (with a validation header).
When I put the files on my server, they both run fine in all browsers, so the issue seems to be the cross domain restrictions.
In reference to the SOAP call, I was wondering if I could use an httpHandler (ashx) page to redirect to the asmx web service running on the same server to resolve this issue when all the client calls will be from different domains.
Would that work and do you have any examples of how to call a method in an asmx web service through an httpHandler when the request originates from other domains?
I’ll be awfully relieved if you do :-)
Thanks for your great articles and your help.
Lonnie
As it turns out, I do have a post covering exactly that topic: http://encosia.com/use-asp-nets-httphandler-to-bridge-the-cross-domain-gap/
Hope that helps.
Dave ,
I have a similar requirement where my mobile html website(site1) need to re-use Login module from another website(site2). Can I create a asp.net web method in site2 and post the user name and password cross domain and get response back ? seems like it should be possible.
I want to restrict access to the rest of the pages in site1 , if the user is not authenticated . Once authenticated, how can I make sure subsequent requests in the same session to site1 are authenticated too? we can’t set cookies cross domain can we using cors request ? can we?
Thanks in advance.
Cookies should work cross-domain transparently. If a response from site2 sets an auth cookie, subsequent requests to site2 will include that cookie.
I am trying to do CORS and successfully configured it using this article. I tried CORS because I am connecting WCF service with remote client on different domain. Initially I tried JsonP and was happy to use it . But faced problem of error handling and server response due to JSONP inbuilt issues. I was able to debug the WCF service using JSONP calls but then i read about CORS and shifted towards it.
I tried to run WCF in debug mode but the above solution to enable CORS is not working in ASP.NET development server . Althoug it runs on IIS hosted WCF . using CORS now it is becoming difficult to debug and track error on WCF and business layers. Any help .
Can you use IIS Express locally for development? Then the CORS-enabling web.config entries will have the same effect.
Hello Dave,
It works and great breeze to use now. I am good to go with CORS now but meanwhile i am still not sure to go for CORS or use JSONP . I am getting an idea that for GET calls its good to use JSONP and for CRUD operations i need to use CORS.
I am developing application using backbone and WCF . Will make a version for mobile apps as well. Could you comment on usgae of JSONP or CORS in this respect.
Thanks ,
If you’re targeting browsers that support CORS, I would always prefer CORS over JSONP.
I can understand the reason of preferring CORS over JSONP. I was not find to run the CORS on firefox 14.0.1 and getting error 405-method-not-allowed,but using the following article it worked now on firefox and chrome
http://blog.weareon.net/calling-wcf-rest-service-from-jquery-causes-405-method-not-allowed/
Thanks,
Any idea if the Visual Studio 2010 development server honours these Access-Control-Allow-Origin headers?
I’m hosting few WCF services in my own host (using ServiceHost) and I have the below section in app.config, but the custom headers are not added to any response.
The development server does not, but IIS Express should if you can use that.
Hi Dave,
You won’t believe that I had been struggling badly for cross domains webservice methods. Now everything is working like charm. Special thanks to you and Lonnie too.
I simply made these changes in my web.config and now it responds back properly.
Thank you guys to save many lives.
FWIW and in case folks are interested in finer-grained control over their CORS configuration, I’ve built a spec-compliant open source CORS library for ASP.NET, WebAPI and MVC:
http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/
I followed your steps to modify my web.config file but none of my responses include the custom headers whether I use IIS or the development server. No errors are thrown or anything indicating there is a problem with the web.config file. Any thoughts?
Please, change “httpprotocol” to “httpProtocol” and “customheaders” to “customHeaders” in your example, because you’re top 1 in Google now, and it’s the second time I pasted your snippet to my server and crashed it ;)
Of course thank you for great info, I learned a lot about CORS from this article and I use it now on my production servers.
BTW, if you place web.config in subdirectory of IIS web application, you have to convert this subdirectory to application first, without this step all I got was error 500 ;)
I’m not sure when/how those section names lost their casing – they were originally copy/pastes from Visual Studio. They’re fixed now. Thanks for pointing that out.