home | articles | site map | contacts
about us
consulting
client login
support
contacts
  Cross Domain Policy Violation, And How to Get Around It (JSONP, AJAX, JavaScript)
  8/29/2011 (Modified 9/12/2011)
Follow PrimaryObjects on Twitter Subscribe to Primary Objects via RSS More Software Articles
by Primary Objects
enter email address
 

Introduction

Web services are an important part of the architecture for many software systems. While many web applications use JavaScript AJAX requests to make calls to local web service methods, occasionally the need arises to call outside web services to retrieve data for the current request. Examples include mashup web applications, interconnected business applications, or web page widgets (for example, Google Adsense, Twitter scrollers, Facebook comment streams, etc). While client script can certainly call local web service methods, calling outside web services from non-local domains violates the cross-domain policy. To work around this policy, an additional tactic is required.

In this tutorial, we'll create a simple C# .NET web service that creates monsters and returns them in JSON format. We'll create a simple web page that uses a JavaScript AJAX request to display a monster each time a button is clicked. To simulate an outside web service, we'll modify the local PC's hosts file to simulate a new domain, and examine how to get around the cross-domain policy in order to call our web service that is (effectively) hosted on an outside domain.

 
The Problem is Web Browser Security

As AJAX is the driving force behind the majority of next-generation web applications, many developers are running into the issue of cross-domain communication. Cross-domain policy (or same-origin policy) prevents client JavaScript, loaded from one domain, from accessing or manipulating the properties of a document from another domain. In short, the domain being requested from the client script must match the domain of the current web browser page. This policy is enforced by all major web browsers.

Two Ways to Break Cross Domain Policy

A common solution to calling an outside web service is to utilize server-side code to make the request. This effectively utilizes a proxy (where the server-side code [PHP, Ruby, C#, etc] serves as the proxy, making the call to the 3rd-party web service on behalf of the client-side script). You've probably implemented this solution without even realizing that you were handling the cross-domain policy. Typical examples might include calling a weather API web service, Google service, Flickr, data scraping a web page, or other outside web calls. In each request from the client script, a call was made back to the local web application to fetch data, process it, and return it to the local client web browser. The web application was serving as a proxy between the web browser and outside domain.

Server Code as the Proxy Has Limitations

Using server-side code as a proxy is not always an option. In particular, making a call to the local web application, which in turn, retrieves the data from the outside domain and returns it to the client, may be an unnecessary and extraneous call, creating a bottle-neck and potential performance degradation. To optimize the process, the call to the local web application could be bypassed and the client script could call the outside web service directly. This would offload your own web server and move the processing to the client-side.

Another potential issue is if you were creating a simple JavaScript widget for users to implement in their web pages, without needing server-side code. In this scenario, the script would need to load data from your own domain, while running from the users' web sites. It would be unwarranted to ask the user to install server-side code to process the requests. Most users would have no idea how to do this. Instead, your JavaScript would be responsible for making all requests and displaying the results.

In both scenarios above, the web browser would be prevented from making the request due to the cross-domain policy, which prevents requests to outside domains from the web browser. To get around this issue, you'll need to use AJAX with JSONP.

Creating a Monster Service

To demonstrate the cross-domain policy and potential solutions, we'll create a simple web service using C# ASP .NET MVC. Our web service will create a random monster and return its data in JSON format. Our MonsterController can be implemented as follows:
    public class MonsterController : Controller
    {
        public ActionResult Index()
        {
            if (Request.IsAjaxRequest())
            {
                Monster monster = MonsterManager.CreateRandom();

                return Json(monster, JsonRequestBehavior.AllowGet);
            }
            else
            {
                return View();
            }
        }
    }

In the above code, we simply check if the request is an asynchronous request. If it's not, we return the default view. If it is an AJAX request, we create a random monster and return it in JSON format. Calling the above method (ie., "/Monster") would return the following result to the client script:

Output:
{"Name":"Silver Gargoyle","Description":"A very scary Silver Gargoyle indeed!"}

The MonsterController MVC view


We can create a simple web page view to render the monster, with the following Razor markup code:
@model MonsterService.Models.Monster

@{
    ViewBag.Title = "Monster Service Test";
}

<script language="javascript" type="text/javascript">
    function updateMonster() {
        var date = new Date();

        $.ajax({
            url: '/Monster?ticks=' + date.getTime(),
            success: function (data) { onComplete(data); },
            error: function (xhr, ajaxOptions, thrownError) {
alert(xhr.statusText); alert(thrownError); } }); } function onComplete(data) { $('#content').html(data.Name); } </script> <div id="content">No monster. Click Create.</div> <input type="button" value="Click" onclick="updateMonster();" />

In the above code, we have the JavaScript methods for calling our web service and displaying the resulting monster in an HTML div. Our JavaScript will use the jQuery ajax call for asynchronously calling the web service method. Notice, the target url that we call is our local /Monster path. This is permitted within cross-domain policy, since we're not actually crossing any domains to get the data. Note, we need the "ticks" parameter to differentiate subsequent web requests from the cache by appending to the url. If you're interested, you can view the raw JSON string returned by the web service by adding a call to JSON.stringify(data) within the onComplete() method.

Successfully Calling a Local Web Service With a Standard AJAX Request

Using the above code, we'll call our local web service using the following block of JavaScript:
    function updateMonster() {
        var date = new Date();

        $.ajax({
            url: '/Monster?ticks=' + date.getTime(),
            success: function (data) { onComplete(data); },
            error: function (xhr, ajaxOptions, thrownError) { 
alert(xhr.statusText); alert(thrownError); } }); }

This will utilize jQuery to make an XMLHttpRequest call to retrieve the data from our local web service method. Running the sample would produce the following output:

Output:
No monster. Click Create.
Black Hippogryph
Silver Serpent
Cunning Gargoyle

As you can see in the example, our client script was able to successfully load and display the monster data from the local web service. Since the cross-domain policy was not violated, we were able to call the local web service and return the result.

Hacking the Hosts File to Simulate Cross Domain Policy

Let's take a look at what happens if we try to call an outside domain with the same client-script code. We can simulate an outside domain by modifying our hosts file. Locate your hosts file in C:\Windows\System32\drivers\etc and edit the file "hosts" to add a new entry. You can redirect any domain to 127.0.0.1, which is your local PC. All requests to this domain will now be sent to your local PC (and thus, your local web application), although the web browser will still believe this is an outside domain.

Modifying the local PC's hosts file to simulate an outside web server and cross-domain request:
C:\Windows\System32\drivers\etc
127.0.0.1       me.primaryobjects.com

AJAX Fail

By modifying our hosts file, we've effectively setup an outside domain name that calls our local web service (for debugging purposes). You could also copy your web service application to a web server with actual hosting in order to test this, although modifying the hosts file is an easier step.

We'll now modify our JavaScript to change the call from a local web service relative path to a remote web service on a simulated third-party domain. We'll be changing our call from "/Monster" to "http://me.primaryobjects.com". This will invoke the cross-domain policy upon the ajax request, forcing an error in the call to XMLHttpRequest. The modified code can be seen, as follows:
    function updateMonster() {
        var date = new Date();

        $.ajax({
            url: 'http://me.primaryobjects.com:17889/Monster?ticks=' + 
                  date.getTime(),
            success: function (data) { onComplete(data); },
            error: function (xhr, ajaxOptions, thrownError) { 
alert(xhr.statusText); alert(thrownError); } }); }
Output:
No monster. Click Create.
error
No Transport
No Transport For You

The above code example returns an error in the jQuery call to .ajax(). The error reports "No Transport", indicating it was unable to make a remote server request (presumably due to the cross-domain policy violation). If you try the URL directly in your web browser, the web service method will execute, which indicates the hosts file redirection is working properly. However, the JavaScript call fails to execute due to the same-origin policy. Since our client script is requesting data from an outside domain (tricked by our hosts file), the web browser terminates the request, resulting in a "No Transport" error. To get around this, we'll need to take advantage of JSONP.

Welcoming JSONP

JSON is a data format, similar to XML, but containing a different notation. Its primary benefit is that it is contains native support within JavaScript for automatically being handled as an object. In JavaScript, JSON is a string representation of an object.

JSONP is a technique that utilizes JSON to circumvent the cross-domain policy and allow client script to communicate with outside web services on different domains. JSONP takes advantage of the fact that while web browser cross domain policy prevents data and documents from being modified on outside domains, it does not prevent script from being downloaded from outside domains. Using this knowledge, JSONP requests web service data, wrapped within a script tag. The source url of the script is the target web service url. The resulting script is then executed by a callback handler in the client, where we can then process the actual data.

It's important to note that while the client script can issue a JSONP request wrapped in a script tag, the web service method will also need to support JSONP by returning the data wrapped in a function. That is, since the client script is using a script tag, the resulting data must be valid JavaScript. If raw data is returned by the web service, the client script would fail to run.

JSONP Example Flow

The client script makes the following call to obtain JavaScript from an outside domain:

<script type="text/JavaScript" src="http://me.primaryobjects.com:17889/Monster"></script>

The web service responds with valid JavaScript, wrapping the requested data in a callback method:

processData({name: 'Name', value: 'Stuff'});

Of course, your client script would need to define the processData(data) method. Rather than impose function names on client developers, the JSONP standard is to include a "callback" parameter in the calling url, to indicate the name of the wrapped method. For example:

The client uses:

<script type="text/JavaScript" src="http://me.primaryobjects.com:17889/Monster? callback=myMethod"></script>

The web service responds with:

myMethod({name: 'Name', value: 'Stuff'});

Your resulting client script then defines "myMethod(data)" and handles the resulting data accordingly. Note, jQuery's getJSON() method takes care of automatically including the callback parameter and even creating a random callback method to grab the data for you.

Fixing our MonsterController to Use JSONP

The modified MVC MonsterController class, using JSONP as the resulting data:
    public class MonsterController : Controller
    {
        [JsonpFilter]
        public ActionResult Index()
        {
            if (Request.Params["callback"] != null)
            {
                Monster monster = MonsterManager.CreateRandom();

                return Json(monster, JsonRequestBehavior.AllowGet);
            }
            else
            {
                return View();
            }
        }
    }

In the above code, we've extended our method to include a filter attribute of JsonpFilter. While our original code returned the data in standard JSON format, we'll now wrap the result in the callback handler, as valid JavaScript. Our filter attribute takes care of this for us.

The JsonpResult Filter Attribute
    public class JsonpResult : JsonResult
    {
        public string Callback { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            HttpResponseBase response = context.HttpContext.Response;
            if (!String.IsNullOrEmpty(ContentType))
                response.ContentType = ContentType;
            else
                response.ContentType = "application/javascript";

            if (ContentEncoding != null)
                response.ContentEncoding = ContentEncoding;

            if (Callback == null || Callback.Length == 0)
                Callback = context.HttpContext.Request.QueryString["callback"];

            if (Data != null)
            {
                // The JavaScriptSerializer type was marked as obsolete
                // prior to .NET Framework 3.5 SP1 
                #pragma warning disable 0618
                
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                string ser = serializer.Serialize(Data);
                response.Write(Callback + "(" + ser + ");");

                #pragma warning restore 0618
            }
        }

AJAX + JSONP FTW

With the above changes made, we can try calling our web service on an outside domain again. However, we first need to make a change to our client script to make it compatible with JSONP requests. This includes using a script tag with the target url being our web service call. We'll utilize jQuery to streamline this for us, as follows:
    function updateMonster() {
        $.ajax({
            url: 'http://me.primaryobjects.com:17889/Monster?callback=?',
            dataType: 'json',
            success: function (data) { onComplete(data); },
            error: function (xhr, ajaxOptions, thrownError) { 
alert(xhr.statusText); alert(thrownError); } }); }

In the above code, we've modified our call to the jQuery ajax() method to include a "dataType" parameter of "json". This tells jQuery to output a script tag and fill in our callback parameter with a random function name. The resulting data will be processed and returned in the "success" method. This is the same manner of reading the returned data as we've used above. Note, we no longer need to include a ticks parameter in the url, to avoid the cache, since the callback parameter will be filled in automatically with a random string.

Running the above code would produce the following output:

Output:
No monster. Click Create.
Silver Wraith
Dark Harpie
Slimy Gargoyle

JSONP and jQuery on Speed

We've successfully bypassed the cross-domain policy, reading and processing data from an outside domain. We can actually take the code one step further in optimization, as follows:
function updateMonster() {
  $.getJSON('http://me.primaryobjects.com:17889/Monster?callback=?', 
            function(data) { onComplete(data); });
}


We've replaced the cumbersome jQuery .ajax() method with a call to .getJSON(), which automatically inserts the "dataType: json" property and streamlines the success method.

With our client-side JavaScript effectively reading an outside domain, we could now create a range of client-side web application widgets, consisting only of JavaScript and HTML. The widget would simply call our outside web service to return the data for display.

And Now a Word on Responsbility

JSONP permits a powerful techinque for writing widget applications that can read data from outside web services with ease. However, it also contains some drawbacks and security considerations. The first item of concern is that JSONP typically provides no error handling from broken links or malformed data. The call will typically simply fail. This can make it difficult to debug problems and recognize errors. Of course, client code can always timeout on a request to avoid prolonged delays, it's still important to keep in mind error handling issues.

A second drawback is the security consideration of executing returned JavaScript from an outside domain. Since JSONP returns JSON wrapped within a function call, the local web browser will execute the returned code. If the web service host or server were compromised, the returned JavaScript could be vulnerable to attack. It's important to keep these items in mind when using JSONP.

A final consideration when using JSONP is that it requires both the client and server to support the technique. It's important to remember that the client must issue a script tag request with the web service url as the source property. Likewise, the web server must issue a response as valid JavaScript, usually wrapping the returned data in a callback handler. As such, JSONP cannot be used on just any outside url. You'll want to take these items into consideration when using JSONP.

Download @ GitHub

The source code for this example is available online in our GitHub repository.

 

About the Author

This article was written by , founder and chief developer of Primary Objects, a software and web application development company. You can contact Primary Objects regarding your software development needs at http://www.primaryobjects.com

 

   
comments powered by Disqus
Profile
Learn more about Primary Objects and our goals ..  More
09/09/2013
Primary Objects releases SentimentView Twitter sentiment analysis engine .. More
05/31/2013
Primary Objects releases ColorBot interactive machine learning, AI .. More
Home | About Us | Services | Client Login | Job Opportunities | Contact Us
Copyright © Primary Objects 2013
Privacy Policy
Follow us on Twitter