The big issue about JSONP is security. If you inject an unknown script in your page, you give to the script author a potential way to read the entire page content, scripts, cookies and data.
So, if you think, like us, the cool way to build web apps today is about building services and aggregating them with external APIs in Mashups, you have a problem. How can we securely fetch these nice services available everywhere?
There is a standard coming out. Douglas Crockford is well known for preaching regularly about it. Here and there, you can also find some good posts explaining the issue and the available solutions. But, to be honest, there is nothing simple to configure and use today.
However, a few weeks ago, I found a post from Kris Zyp (Sitepen), who came with an elegant solution: By abusing the window.name property, he found a way to exchange bigger cross domain text data between a page and its children iframes.
I jumped into the code and the test page. But… it needs a new type of cooperation from the server. This one needs to fill in the window.name property when the resource is called.
Today JSONP is widely available, and our web app will call lots of them. To add some extra protection we found a surprisingly simple approach described below.
The objective of this post is twofold:
First, get your feedback to rapidly know if we missed something 😉 and have to heartbrokenly look at a heavier approach such as gears or a server proxy.
And secondly, let know the Mashup builders that there may be another temporary reliever while waiting for a standard approach.
You can download the files on Github. Hereunder is how it works:
- In your application page, add sandbox.js and call the function sandboxCall. An example can be found at https://beebole.com/myapp
- The function sandboxCall inserts an iframe to the page. Now here’s the trick: the source of the iframe will point to a domain you own. The API URL to inject is passed as a parameter. We’ve set http://beebole-sandbox.com/ as an example. (a)
- The script is injected in the sandbox page. (b)
- The JSON is fetched from the API provider.
- Then, following the JSONP logic, the function callback is called. This function does a single task: it assigns the JSON text to the window.name property.
- We can come back to our main page on beebole.com, but window.name can’t be read as the location is at beebole-sandbox.com (Same Origin Policy). Now comes THE idea of Kris Zyp to read window.name. We change the location back to main page’s domain. In our example we return to the page: https://beebole.com/myapp/local.html. Marko Mrdjenovič, in its jQuery implementation tries to get /robot.txt or /crossdomain.xml which are common files found in the root of web servers. Any existing local file is ok. (c)
- An onload event is fired in our main page. The location has been changed to a local URL.
- We can now access the window.name of that iframe and read/parse the JSON.
A malicious script can only make damage to the sandbox page, which is empty and destroyed as it is loaded. And as long as you do not eval directly the text in your page but use a JSON parser that will only get JSON text and reject the rest, your main page shouldn’t be hurt either. (d)
- (a) We used a new domain name beebole-sandbox.com. A sub-domain like sandbox.beebole.com could do the trick too.
- (b) For this post, we used PHP for the sandbox page. It is widely available and easy to test. A simple static HTML page was working fine in all browsers, except… for the usual clunky one. You can see this page at http://beebole-sandbox.com/all_but_IE.html
- (c) From the Kris Zyp implementation, it looks FF2 needs an additional nested iframe to be secure on that browser.
- (d) This is not 100% bullet proof. The iframe trick does not protect against CSRF if the API provider does not use some basic protection. However, if correctly used, this allows seamless integration between APIs
We know window.name is a trick. It is also already used by Andrea Giammarchi and Thomas Frank. Hopefully when postMessage or another standard approach will be available, window.name won’t be necessary anymore.
It was tested on FF3, Safari3, Opera 9, IE6-7, Google Chrome and iPhone.
Too good to be true? Do you see some immediate improvement? Do you see any holes? Please, tell us.
For now, we are happy with just JSONP/GET. POST, DELETE could be done too.
A higher-level JS function could be built to use postMessage when available. If you have ideas about it, please fork the repository at http://github.com/beebole/sandbox
Thanks for any feedback.
Other links of interest: