Cross-Site Scripting with JSONP

While JavaScript's XmlHttpRequest object does not allow you to load data from outside of the page's current domain because of the same origin policy, it turns out that SCRIPT tags are not subject to the "same origin" policy. Consequently, an informal standard known as JSONP has been developed that allows data to be cross-domain loaded. As you might imagine, it is this very capability that empowers web applications[13] to mash up data from multiple sources and present it in a single coherent application.

JSONP Primer

Like anything else, JSONP sounds a bit mysterious at first, but it is pretty simple once you understand it. To introduce the concept, imagine that a SCRIPT tag is dynamically created and appended to the HEAD of a page that was originally loaded from http://oreilly.com. The interesting twist comes in with the source of the tag: instead of loading from the oreilly.com domain, it's perfectly free to load from any domain, say http://example.com?id=23. Using JavaScript, the operation so far is simple:

e = document.createElement("SCRIPT");
e.src="http://example.com?id=23";
e.type="text/javascript";
document.getElementsByTagName("HEAD")[0].appendChild(e);

Although the SCRIPT tag normally implies that you are loading an actual script, you can actually return any kind of content you'd like, including JSON objects. There's just one problem with that—the objects would just get appended to the HEAD of the page and nothing interesting would happen (except that you might wreck the way your page looks).

For example, you might end up with something like the following blurb, where the emphasized text is the result of the previous JavaScript snippet that dynamically added the SCRIPT tag to the HEAD of the page:

<html>
  <head>
    <title>My Page</title>
    <script type="text/javascript" >
      {foo : "bar"}
    </script>
  </head>
  <body>
Some page content.
  </body>
</html>

While shoving a JavaScript object literal into the HEAD is of little use, imagine what would happen if you could somehow receive back JSON data that was wrapped in a function call—to be more precise, a function call that is already defined somewhere on your page. In effect, you'd be achieving a truly marvelous thing because you could now asynchronously request external data whenever you want it and immediately pass it into a function for processing. To accomplish this feat, all that it takes is having the result of inserting the SCRIPT tag return the JSON data padded with an extra function call such as myCallback({foo : "bar"}) instead of just {foo : "bar"}. Assuming that myCallback is already defined when the SCRIPT tag finishes loading, you're all set because the function will execute, pass in the data as a parameter, and effectively provide you with a callback function. (It's worth taking a moment to let this process sink in if it hasn't quite clicked yet.)

But there's still a small problem: how do you get the JSON object to come wrapped with that extra padding that triggers a callback? Easy—all the kind folks at example.com have to do is provide you with an additional query string parameter that allows you to define the name of the function that the result should be wrapped in. Assuming that they've determined that you should pass in your function via the c parameter (a new request that provides c as a query string parameter for you to use), calling http://example.com?id=23&c=myCallback would return myCallback({foo : "bar"}). And that's all there is to it.



[13] Without loading any external plugins, JSONP is your only means of loading cross-domain data. Plug-ins such as Flash and ActiveX, however, have other ways of working around the "same origin" limitation that is placed on the browser itself.

Get Dojo: The Definitive Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.