Cross-domain inter-frame communication in javascript
Requirement:
Web-page A from domain A' loads web-page B from domain B' into an iframe. Web-page B wants to be able to render some content into the DOM of web-page A (outside of the view-port described by B's iframe). The content which B renders into A needs to be able to HTTP GET and POST data back to the domain B', handle the responses, and update the rendered content in web-page A.
Problems:
- Scripts loaded into pages from different domains cannot interact so, for example, page B's scripts cannot simply* render content into the parent frame (page A)
- Page A cannot simply* use XMLHTTPRequest to GET/POST/PUT/DELETE to host B
[*] I say "simply" because you can't just expect it to work like it would in a same-domain environment, but it is possible!
Escaping from an iframe
Lets start with rendering content into a parent frame. To get concrete, lets say that domain A is www.domain.com, while domain B is sub.domain.com.
Yes, they are sub-domains of a common root. No, B is not allowed to modify the DOM of A, or communicate with scripts in A, because web-browsers won't allow that unless the ports, protocols, and domains match exactly.
The only way for B to escape from the iframe is to have co-operation from A. That co-operation can come in one of two forms:
- Both pages must explicitly set the
document.domain
property to the same value (e.g. domain.com). Even if one of the pages is served directly from "domain.com", the act of explicitly setting the domain is required for this technique to work - it signals to the browser that the two pages want to collaborate. - Have host A serve an iframe-buster page (more below)
Iframe Buster
Host A can serve a page which loads scripts on behalf of B, sometimes known as an iframe-buster.
This is a common technique in the ad-delivery world to allow complex ads like page take-over's to escape from the iframe they are loaded into. Note that this is not an exploit as such, since it requires host A to be complicit.
To illustrate how it works, here's what a very simple iframe-buster might look like:
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<body>
<script type="text/javascript" language="javascript">
var _url = "http://www.domain.com/bust.js?" + document.location.search;
var _script = document.createElement("script");
_script.setAttribute("type", "text/javascript");
_script.setAttribute("src", _url);
document.body.appendChild(_script);
</script>
</body>
</html>
The host-page A will initially load a page from host B. That page B will load the iframe-buster on host A with some parameters which can be used to direct what the bust-out actually does.
Rendering into the parent DOM
Now that we have assistance from the host-page domain, our iframe can communicate directly with the DOM and scripts in the parent frame, using the window.parent
handle.
var _ctx = window.parent.document;
var _div = _ctx.createElement("div");
_div.innerHTML = '<h1>Hooray!</h1>';
_ctx.body.appendChild(_div);
Great!
Making HTTP requests
Now we want to fetch some JSON data from host B by HTTP GET, and render it in the parent frame. For GET requests we might be OK - we just need the host API to support JSONP. If it doesn't we need one of the other techniques as for making POST requests …
What if we want to POST some data to host B? We can't use XMLHTTPRequest to POST from A to B, as the browser security policies won't allow it. So, what are our options?
- HTML Form POST
- CORS (Cross Origin Resource Sharing)
- Pipelined communication through another frame
HTML Form POST
We could use a form POST, which is allowed to POST to another domain (mostly because the HTML Form POST spec pre-dates the tightened security policies), and will receive the response.
You'll need to do a bit of scripting to wrap things up so that you can register callbacks and have things behave similarly to an XMLHTTPRequest.
This method has the advantage of broad browser compatibility, but the implementation is by necessity less clean, and you lose some of the advantages of XMLHTTPRequest (e.g. the ability to check the response status code).
If you're dealing with a pure RESTful API you'll struggle without the ability to check status codes.
If you have help from the server-side you can probably engineer your way around most of the problems, and even tunnel non-POST API calls by using hidden FORM params and a server-side intercept (e.g. Servlet Filter) to translate the request for you before it hits the API handlers.
That said, if you have control of (or co-operation from) the server-side you'll probably want to look at one of the other methods below.
Advantages:
- Good browser compatibility
- Easily understood
Disadvantages:
- Poor handling of pure RESTful APIs
CORS (Cross Origin Resource Sharing)
We could use CORS, which involves the web-server B checking and sending additional HTTP headers.
This requires a relatively modern browser and some server-side work to check and set additional HTTP headers. CORS is nice because it allows us to conveniently use XMLHTTPRequest for all of our requests (and no need for JSONP).
CORS might put a little extra demand on your servers, as browsers "pre-flight" requests as part of the CORS protocol.
Advantages:
- Just like working in a same-domain environment (good for RESTful API handling)
- CORS is an emerging standard, so you don't necessarily need to own/operate the host for this method to be a realistic possibility
Disadvantages:
- Requires modern browser
- Requires that the host supports CORS
- Some HTTP request overhead (pre-flight)
Pipeline communication through another iframe
A third option is to pipeline your HTTP calls through another iframe - loaded from the domain of the host you want to make calls to.
In newer browsers we can use window.postMessage
to send text between frames loaded from different domains.
Since this text can be JSON, and you can register event-handlers for the "message" event, you can set up a communication-frame per host that you need to talk to, and from inside that frame you can use straight-forward XMLHTTPRequest calls, same-domain style.
There are some neat libraries that use a variety of fallback methods (message-passing via window.name; flash) to make this work in older browsers. The most popular one seems to be EasyXDM.
Advantages: 1. Good browser compatibility (use libraries like EasyXDM) 2. Good for RESTful API handling
Disadvantages:
- More complex set-up
- You need control of the host
- There's some small overhead in piping everything as strings through nested iframe's
Summary
As with everything, there is no one-size-fits-all solution, and some flexibility and compromise is likely to be necessary. For the project I'm working on currently I'm using iframe busters, a little CORS, and a lot of pipelining through another frame, but YMMV.
blog comments powered by Disqus