RELATED: "How Secure is Ajax?" and "Ajax Features in ASP.NET MVC."

For security reasons, browsers tend to unilaterally block any calls being made to URLs outside the domain that served the current page. An Ajax page served from, say, dino.com, isn't allowed to place any Ajax calls to URLs that are located on a different domain. It should be noted that there are no technical reasons that prevent browsers from opening a socket to an endpoint and sending an HTTP request. If that doesn't happen, it's because browsers simply don't want to make cross-domain calls. To confirm this fact, consider that as a user you can enable cross-domain access at any time. All you need to do is open the Options dialog box of a browser, select the Security tab, and change the settings. Figure 1 shows the dialog box you use with Internet Explorer (IE) to enable cross-domain access.

Figure 1: Dialog box for enabling cross-domain data access in Internet Explorer

As a developer, you can't just assume that end users will set their browsers to allow cross-domain calls. Another solution must be found and applied. Indeed, a few options exist to solve the cross-domain access puzzle. In this article, I'll first briefly touch on the current de facto solution, "JSON with padding" (JSONP), then focus on a more futuristic solution that I hope will be available once it's ratified by the World Wide Web Consortium (W3C).

JSONP: The Current Standard for Cross-Domain Access

Although browsers seem reluctant to make cross-domain Ajax calls, they blissfully place cross-domain requests to scripts, images, and HTML pages. In fact, tags such as <script>, <img>, and <iframe> can serve any content from any source regardless of cross-origin security policies. So in theory, you can conceive of pointing a <script> tag to a remote URL and having it serve some content, even dynamically. The problem is, how would you consume this content? A browser would download any content pointed to by a <script> tag, but there's no automatic way to process that content by passing it to some function. This is precisely the scenario that JSONP addresses.

JSONP is a convention used by some sites to expose their content in a way that makes it easier for callers to consume data via script, even from an external domain. The trick consists in having the site return some JSON content not as a plain string but wrapped up in a script function call. For example, a JSONP-compliant remote endpoint would return a string like this one:

someFunction("{...}");

The name of the wrapper function will be provided by the caller as an additional parameter; all that the endpoint does is to get the regular JSON string it would have returned anyway and wrap up the string as an argument passed to the provided script function. This trick now enables you to use a <script> tag like the following:

<script type="text/javascript">someFunction("{...}");</script>

As you can see, this code is now executable, and it will actually execute as soon as the response is received. JSONP requires that the endpoint and caller agree on a way to pass the name of the wrapper function. Subsequently, you can't use JSONP with just any site but only with those that explicitly support it.

Thanks to the support that jQuery provides for it, today JSONP is probably the de facto standard for safe cross-domain data access. Using JSONP is no more risky than downloading a script file from a remote site.

Here's an example of a call in jQuery that uses JSONP to enable cross-domain data access:

var url = http://server/some/endpoint?callback=?
$.getJSON(url, function() {...});

As you can see, the URL has a query string parameter set to a ? symbol. The name of this parameter must be agreed upon between the caller and server. For example, Flickr -- one of the first websites to support JSONP -- requires you set it to jsoncallback. The name is arbitrary but must be properly documented to allow others to make calls to your server. Callers should set the JSONP query string parameter to the name of the local function they want to act on the data returned from the remote site. So why is jQuery setting it to a wildcard?

The trick is that the jQuery programming model pushes the use of anonymous functions as callbacks, so it would be natural to pass such a function as an argument to $.getJSON. In between your call and the callback acting on returned data, though, is the JSONP wrapper. By adding a ? wildcard to the URL, you tell jQuery to generate on the fly a script function with an auto-generated name that's passed to the JSONP server. The body of this auto-generated function will then ultimately call into the specified anonymous function.

Figure 2 contains output from the Fiddler freeware tool showing the traffic generated by the JSONP call in the previous code sample.

Figure 2: Traffic for a JSONP call using jQuery

In Figure 2, the name of the temporary jQuery function is "jQuery" followed by a GUID, and (230) is the value returned by the remote server. (For the sample URL tested, this is just a randomly generated number.) You can try out the JSONP call yourself by pointing your Ajax page to http://www.expoware.org/demos/cors/cors/getdatajsonp and using "callback" as the JSONP parameter name.

Cross-Origin Resource Sharing

Although fully functional, the JSONP technique for making a cross-domain call is based on a clever trick. In particular, jQuery uses the trick by creating a <script> tag on the fly and making it point to a mangled version of the URL you provide. In today's world, being unable to connect to any URL you want from an HTML page is a significant restriction. What could be wrong and risky -- some say -- in a cross-domain call if the remote site agrees to receive calls from some domains? To prove this concept true and provide a definitive answer to the problem of cross-domain access, the W3C is working on a new specification called Cross-Origin Resource Sharing (CORS).

Per the CORS specification, the caller will send a request by adding an Origin header set to the requesting domain. In case of success -- that is, if the request is approved by the remote endpoint -- the caller will then receive a response that includes a particular header. The header that denotes success of the request is named Access-Control-Allow-Origin and is set either to * or to the name of the requesting domain.

The CORS specification is much more sophisticated than this explanation might suggest. However, this explanation covers just the most common scenario: when the caller needs to place a simple GET/POST request with no other additional HTTP header, such as Modified. You can refer to the W3C draft for a formal description of standards, or check out the Mozilla Developer Network's HTTP access control page for a more developer-oriented explanation.

CORS and XMLHttpRequest

CORS is only a specification; to enable CORS to be used in real applications, some type of code wrapper is necessary. What could be better than using XMLHttpRequest to wrap up the CORS logic?

The following code snippet shows the low-level steps followed by any JavaScript libraries that do some Ajax (including $.ajax in jQuery):

var xhr = new XMLHttpRequest();
xhr.open("get", url, false);
xhr.send();

The actual socket is opened only when the flow reaches the send method. The open method, instead, is where cross-domain security restrictions are applied. If the method detects that the URL you passed is outside the current domain, then the method may deny the call in respect of the user's browser security settings. The browser then displays a message box with the nefarious Access is denied message.

An XMLHttpRequest object that supports CORS, instead, would attempt the call anyway by adding the extra Origin header and looking at the response. If the response contains the Access-Control-Allow-Access header, it means the request was accepted and the body may contain the results. Otherwise, the request was denied and no results should be shown to users.This latter point is not trivial as it relates to the actual behavior of the server endpoint. Depending on the implementation, the endpoint could return a response even when it denied the call. Regardless of the response body, the expected browser behavior, though, is to consider the call failed if the Access-Control-Allow-Access header is missing. Figure 3 shows a possible implementation for an ASP.NET MVC CORS endpoint.

public class CorsController : Controller
{
    public JsonResult GetData()
    {
        var number = new Random().Next(1, 1000);
        return new JsonCorsResult
                    {
                        Data = number,
                        JsonRequestBehavior = JsonRequestBehavior.AllowGet
                    };
    }
 }

public class JsonCorsResult : JsonResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        // Grab the requesting domain
        var domain = context.HttpContext.Request.Headers["Origin"];
        if (String.IsNullOrEmpty(domain))
            return;

        // Check domain (or allow all domains)
        //if (!String.Equals(domain, "somedomain.com"))
        //    return;

        // Allow the request and return a response
        context.HttpContext.Response.AddHeader("Access-Control-Allow-Origin", domain);
        base.ExecuteResult(context);
    }
}

Figure 4 shows the traffic observed using Fiddler when a CORS call takes place.

Figure 4: Traffic for a CORS call

CORS and IE

Microsoft has provided a proprietary solution to cross-domain data access since IE8. In IE8 and IE9, you can use the XDomainRequest object to place a request to a cross-domain site and have a good chance of not being denied by the browser. The idea behind XDomainRequest is similar to CORS, at least for simple GET/POST requests. Also with XDomainRequest, the remote endpoint indicates whether it will allow remote callers to access data by including the Access-Control-Allow-Origin header in the response. The difference is that IE's implementation of XMLHttpRequest doesn't acknowledge CORS-like features, and developers must resort to using a different object in the browser object model. Here's an example:

var xdr = new XDomainRequest();
var rnd = Math.floor(Math.random() * 100000);
xdr.open("get", url + "?" + rnd);
xdr.onload = function () {
    $("#Results").html("IE :: " + xdr.responseText);
};
xdr.send();

Developers will be happy to know that Microsoft has stated that IE10 will be fully compliant with the CORS standard.

JSONP via jQuery Now, CORS Later

CORS is incorporated in Level 2 of the XMLHttpRequest specification. However, few browsers support CORS at present. CORS is supported in Firefox 3.5 and later and in the latest versions of Safari and Chrome. While we look forward to widespread support for CORS, for today's needs, JSONP via jQuery is the easiest way to achieve cross-domain data access.

Dino Esposito is a trainer and consultant specializing in web, social, and mobile integrated architecture and effective code design principles and practices. He's the author of Programming Microsoft ASP.NET 4 and Programming Microsoft ASP.NET MVC3 (Microsoft Press).