Telerik blogs

CORS is cool. Cross-Origin Resource Sharing is a (slowly) emerging technology for the web that finally gives async web operations a way to directly grab resources from different domains. In fact, I've already talked about it a couple of times on the Kendo UI blogs here and here.

By default, the "same origin" security sandbox built-in to all browsers does not allow XHR (Ajax) calls across different domains. You can try, sure, but you'll get an error that looks something like this:

XMLHttpRequest cannot load [URL]. Origin [YOUR WEBSITE] is not allowed by Access-Control-Allow-Origin.

This is basically a message that says you can't use Ajax to load resources from a different domain. But what's that last part of the error?

Access-Control-Allow-Origin

CORS works by adding a special header to responses from a server to the client. If a response contains the Access-Control-Allow-Origin header, and if the browser supports CORS, then there is a chance you can load the resource directly with Ajax - no need for a proxy or JSONP hacks.

Why just a chance?

The header is capable of specifying which remote sites are allowed to load the cross-origin resources. For example, consider the following CORS header in a response from kendoui.com:

Access-Control-Allow-Origin: http://htmlui.com

With this configuration, only scripts that originate from http://htmlui.com are allowed to load resources from kendoui.com. Any other domain trying to use Ajax to load resources from kendoui.com will be given the standard security error message. In this way, site owners can limit which domains are allowed to load their resources with CORS.

Alternatively, site owners can grant wide-open access with the always ready to party asterisk:

Access-Control-Allow-Origin: *

Now, any site that wants to load a resource directly using Ajax can do so without getting the browser security error. It's really a thing of beauty, and hopefully more modern web APIs will start to support CORS. You can already find CORS in action with the GeoNames.org and Last.fm APIs (but not on Twitter or Facebook…boo…).

Server-side Setting

Clearly, CORS is powerful. It opens-up the tightly controlled browser security sandbox that is essential to the trusted fabric of the web. As you might expect, then, it's a decision that must be made by site owners (you can't use CORS with sites that don't allow it), and it's controlled by web server configuration.

Anyone can "CORS-enable" their site by simply having the web server add the necessary Access-Control-Allow-Origin header. In fact, there is an entire website dedicated to showing you how to add this header for a host of different web servers.

Dealing with Browsers

Ah, browsers. It's never simple with browsers.

As confirmed by the super-useful CanIUse.com, support for CORS is a bit of a mixed-bag. CORS is 100% ready to roll in:

  • Webkit browsers (Chrome, Safari, iOS, Android)
  • Gecko browsers (Firefox)
  • Trident browsers (Internet Explorer 8+)**

That's not bad. In fact, the only major browser completely missing in this list is Opera, which is unusual. Usually Opera is a good early adopter of popular web standards. Go figure. Fortunately, Opera claims it is "actively working" on adding CORS support, so soon Opera will "complete" the browser support story.

We'll talk about Opera more in a minute, but let's first address IE.

Internet Explorer and XDomainRequest

It's always something IE, and with CORS it's the XDomainRequest object. Rather than go the route of Webkit and Gecko, IE does not reuse the XMLHttpRequest object for CORS requests. Instead, it introduces a brand new object for cross-origin resource sharing called XDomainRequest.

So while your Ajax code for cross-domain calls looks 100% identical to "same-domain" calls in Chrome and Firefox, it will have to "fork" in Internet Explorer to use the new XDR object with CORS requests. A pain, but a solvable problem. (There are some other limits with XDR, but I'll leave that to you to research.)

With this small snippet, we can write code that works with CORS XHR, XDR, and when that doesn't work, some kind of fallback approach:

//Detect browser support for CORS
if ('withCredentials' in new XMLHttpRequest()) {
    /* supports cross-domain requests */
    document.write("CORS supported (XHR)");
}
else{
  if(typeof XDomainRequest !== "undefined"){
     //Use IE-specific "CORS" code with XDR
     document.write("CORS supported (XDR)");
  }else{
     //Time to retreat with a fallback or polyfill
     document.write("No CORS Support!");
  }
}

(You can try this code snippet live in different browsers to confirm it works.)

The XMLHttpRequest2 object, which includes required support for CORS, can be used to feature detect browser support for CORS. If the browser's XHR object has the XHR2 "withCredentials" property, we're in business. If not, step 2.

In step 2, we take one more attempt to use CORS by looking for IE's proprietary XDomainRequest object (which works with the same Access-Control-Allow-Origin headers, by the way). If the XDR object exists, we're in IE and we can write the necessary code to do XDR CORS.

Finally, if all else fails (Opera), we have to either provide an alternate experience or find a hack for CORS.

Handling Opera (or non-CORS browsers)

Assuming we reach the point where XHR2/CORS and XDR are not available, what's the next step? Abandon CORS? Yell at the user? As it turns out, you have a few choices. You can:

  1. Use a CORS polyfill
    There are a couple of rather "hacky" polyfills for CORS that rely on either A) Flash under the covers, or B) some voodoo HTML5. Either way, they work around the browser limits and try to give you some semblance of CORS support.
  2. Use JSONP
    Of course, if you can use JSONP, the argument can be made that you should just use JSONP instead of CORS for all browsers since it is still more universally supported. But let's say you're trying to "evolve" past JSONP except when you absolutely need. In that case, you can fallback to XHR and JSONP callbacks with Opera.
  3. Display an error message
    The most draconian of your choices, but given most browsers do support CORS, you could simply elect to tell the less than 2% (on average) users of Opera to use a different browser. Just depends how important Opera traffic is to your site.

The choice is yours, but clearly, you have some choice. Fortunately, we're talking about Opera and not IE, so the share of the browser market in question affected by this scenario is comparatively small.

Putting It All Together

In my Kendo UI Feed Reader demo, I use YQL to feed RSS XML directly to the browser. YQL supports CORS, so I elected to send XML to the browser instead of JSONP to highlight Kendo UI's data source support for XML.

Unfortunately, I overlooked IE and Opera in v1. To add support for these browsers, I modified the code to use XDR with IE and YQL JSONP with Opera.

Fixing IE with jQuery $.ajax transports

Fixing IE was actually very easy thanks to some code written by Derek Kastner. With jQuery 1.5+, you can create custom "transport" implementations to control the inner-workings of jQuery $.ajax. Derek has done just that with XDR.

His "iecors" project simply creates a jQuery transport that uses XDR in jQuery $.ajax requests when it's needed. All I have to do is add the small JS file to my page and everything starts working. The Kendo UI Data Source, which uses jQuery $.ajax under the covers, will now use XDR in IE.

So, I just add this to the bottom of my page, after jQuery but before any Kendo script references:

<!--[if lt IE 10]>
<!--iecors provides a jQuery ajax custom transport for IE8/9 XDR-->
<script src="scripts/jquery.iecors.js"></script>
<![endif]-->

Fixing Opera with More Code and JSONP

Unfortunately, fixing Opera is not as easy. And, ultimately, there is no clean way to do CORS in Opera. My choices were to either display a "browser not supported" message for Opera users OR bite the bullet and "fallback" to YQL JSONP when CORS is just not going to work.

I elected to use the later approach.

//**HACK for OPERA (and non-XHR2/XDR browsers)
//For lack of a reasonable Opera workaround to support CORS, fallback to use
//YQL support for JSONP when dealing with a browser than doesn't support 
//CORS XHR or XDR
if (!('withCredentials' in new XMLHttpRequest()) && !(typeof XDomainRequest !== "undefined")){
    _feedItemDS = new kendo.data.DataSource({
        transport:{
            read:{
                url: "#",
                dataType: "jsonp"
            },
            dialect: function (options) {
                var result = ["callback=?","format=json"],
                    data = options || {};
             
                return result.join("&");
            }
        },
        schema:{
            type:"json",
            data:"query.results.rss.channel.item",
        }
    });
//**END OPERA/Non-CORS HACK
}

Now, Opera (any other non-CORS browser) will use an alternate configuration of the Kendo UI data source pointed at a JSONP endpoint and expecting a JSON response. Pretty? No. Functional? Yes. Sometimes, that's what it takes to build software that runs in every major browser and platform.

Of course, you can see everything in action by visiting the updated Feed Reader demo. Full source and context are a right-click, view source away.

Bottom Line on CORS

Going forward, do you think the Kendo UI Data Source should help with some of these scenarios by automatically handling CORS in browsers like IE? Should something like "iecors" be built-in to Kendo UI if jQuery core continues to omit it? Would it be useful to see this as a core feature in the Kendo UI Data Source? Let us know what you think.

Ultimately, if you really need to support Opera, CORS is probably more work than it's worth right now. Just stick with JSONP. Meanwhile, supporting CORS in IE isn't hard as long as your use of CORS stays inside of XDR's limits, so IE, Firefox, Chrome, and Safari are safe bets.

Hopefully this post helps highlight the value of CORS and how it can be used with most modern browsers. As more app code moves to the client, the need for CORS will only grow. Start playing with it today and help push web standards to the next level.


ToddAnglin_164
About the Author

Todd Anglin

Todd Anglin is Vice President of Product at Progress. Todd is responsible for leading the teams at Progress focused on NativeScript, a modern cross-platform solution for building native mobile apps with JavaScript. Todd is an author and frequent speaker on web and mobile app development. Follow Todd @toddanglin for his latest writings and industry insights.

Comments

Comments are disabled in preview mode.