How JSONP works (and some bits about implementing it in WCF)


In the world of the web, we have lots of security concerns. One of the concerns lies with cross site scripting, XSS. From a high level, XSS is any occasion where data is sent from code on a page from one site to another site. The code is usually via JavaScript, though flash and Silverlight are included in the terminology and mitigations. One way around this that is generally viewed as safe is JSON with Padding, aka JSONP. The way JSONP works is this: you pass an HTTP GET request to retrieve a resource from another site which you do not control. The request contains two pieces of information:

1. The resource you want to retrieve.

2. The callback function that should be executed when the resource is returned.

What makes this safe? The callback function is always something that you control. It is assumed that if your callback does bad things, so be it; that problem existed without involving JSONP.

There is a convention around JSONP. The URL that you call needs to understand a querystring parameter named callback, which identifies the function to call when the method returns. How does this work? Assume we have a resource that, when no callback is specified, returns the following when requesting the resource at

http://localhost/WcfJsonp/jsonp/names:

[{"FirstName":"Scott","LastName":"Seely"},

{"FirstName":"Aaron","LastName":"Skonnard"},

{"FirstName":"Matt","LastName":"Milner"}]

With JSONP, we would like a GET of the above data to execute a function on our page named updatePeople. JSONP indicates that we do this with a request for the resource at http://localhost/WcfJsonp/jsonp/names?callback=updatePeople. In so doing, the resource is now returned as:

updatePeople(

[{"FirstName":"Scott","LastName":"Seely"},

  {"FirstName":"Aaron","LastName":"Skonnard"},

  {"FirstName":"Matt","LastName":"Milner"}]);

This causes the function, updatePeople, to be called as soon as the resource is retrieved.

How does this work? Most of us will use jQuery to get the job done and not think about things. But, without jQuery, JSONP is still possible. I’ll first show you how this works by hand, then with jQuery. jQuery eliminates a number of potential bugs, so I recommend reading the by hand portion solely to understand things-don’t implement it in production code!

Assume you have the following HTML body:

 

<body>
    <div>
      <input type="button" onclick="getPeopleJsonp();" 
             value="Get people" />
    </div>
    <div id="people"></div>
    <script type="text/javascript" 
      src="/wcfjsonp/Scripts/jquery-1.4.1.min.js"></script>
    <script type="text/javascript">
        function updatePeople(data) {
          var innerHtml = "";
          for (i = 0; i < data.length; ++i) {
            innerHtml += data[i].FirstName + ' ' + 
                         data[i].LastName + '<br/>';
          }
          $("#people").html(innerHtml);
        }

        function getPeopleJsonp() {
          var innerHtml = "<script type='text/javascript' " + 
                          "src='http://scottseely-xps/wcfjsonp/" +
                          "jsonp/names?callback=updatePeople'></script>";
          $("#inject").html(innerHtml);
        }
    </script>
    <div id="inject"></div>
</body>

What is happening here? When someone clicks on the button, the page calls getPeopleJsonp. That code updates the content of the div whose id is inject to contain a script tag. The script tag points to a JSONP endpoint and passes the callback parameter indicating to call the function updatePeople upon success. That script is then evaluated, executes a local JavaScript function, and returns. Every time that same script is reinjected, the same behavior occurs.

Now, jQuery provides a more succinct mechanism for doing the same thing. Instead of having the previous two functions and updating the DOM ourselves, we would write this:

 

function getPeopleJquery() {
  $.ajax({
    dataType: 'jsonp',
    url: "http://scottseely-xps/WcfJsonp/jsonp/names",
    success: function (data) {
      var innerHtml = "";
      for (i = 0; i < data.length; ++i) {
        innerHtml += data[i].FirstName + ' ' + data[i].LastName + '<br/>';
      }
      $("#people").html(innerHtml);
    }
  });
}

The code to inject information into the DOM still occurs, as does the creation of a callback function. What jQuery does is it adds script tag to the DOM only until the callback (success function) is done executing. The tag is added to the header. It also adds a function to the window object which is called on return. The URL jQuery constructs looks like http://localhost/WcfJsonp/jsonp/names?callback=jsonp1283609644773. What it actually does is add the following:

blog-tagadded

Within the window object, a new function is added:

expandos

jsonp1283609644773 is just an object living on the window object and is a dynamically added function. When the function is done executing, it will remove itself from the window object, leaving a slot by the same name with a value of undefined. What does the function look like when it is alive?

function(q){n=q;b();d();z[i]=v;try{delete z[i]}catch(p){}A&&A.removeChild(B)}

So, yeah, not too intelligible, but it does appear to handle things just fine. b(), d(), and the other functions are all part of the minified jQuery library, meaning that it all makes sense in the context of the containing object. The call to b() is contains code to call the success function. Everything else is cleanup code (at least from what it looked like to me.). To make this whole thing work in WCF, you need to do a couple simple things:

1. Only use WebGet to fetch resources.

2. Make sure the resource is hard-coded to respond in JSON. Otherwise, the response will appear as XML and the callback code won’t execute.

3. Make sure your binding supports JSONP.

4. Make sure your service requires ASP.NET compatibility.

For example, to get the names, I have the following WCF code:

[ServiceContract]
[AspNetCompatibilityRequirements(
  RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class JsonpDemo
{
  [OperationContract]
  [WebGet(UriTemplate = "names", ResponseFormat = WebMessageFormat.Json)]
  public PersonName[] GetNames()
  {
    return new PersonName[]
              {
                new PersonName {FirstName = "Scott", LastName = "Seely"},
                new PersonName {FirstName = "Aaron", LastName = "Skonnard"},
                new PersonName {FirstName = "Matt", LastName = "Milner"}
              };
  }
}

Configuration has:

<system.serviceModel>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
  <standardEndpoints>
    <webHttpEndpoint>
      <standardEndpoint crossDomainScriptAccessEnabled="true"
                        automaticFormatSelectionEnabled="true"/>
    </webHttpEndpoint>
  </standardEndpoints>
</system.serviceModel>

 

The crossDomainScriptAccessEnabled flag turns on the ability to correctly parse and respond to the presence of the callback parameter in the query string.

Finally, I added a route to handle the service within my Global.asax:

var factory = new WebServiceHostFactory();
routes.Add(new ServiceRoute("jsonp", factory, typeof(JsonpDemo)));

I was looking at the technology only to evaluate how to use it and to understand, a bit more deeply, how JSONP actually works. I found myself looking for a post that explains the magic, but most of the information I found just said “use jQuery.” Handy-sure. But that didn’t help me understand all the magic goodness that jQuery was doing for me.

%d bloggers like this: