dira ⋅ geek ⋅ girl

Heroku, S3, canvas and the Security Error of doom

dira

Let’s say you have a nice little project, involving Heroku, S3 and <canvas>. It works like a charm… that is, until you need to do something totally crazy. Such as, stitching some of your images together in a canvas and uploading the result to your application. That should be easy, right? But… browsers throw a security error when you do it, and you find out that you cannot access the data in a canvas that contains images loaded from a different domain.

Update: In September 2012, Amazon added CORS support for S3. See more about how this helps.

Enter postMessage

Cross-document messaging, represented by postMessage, a little-known hero of the browser world, comes to the rescue. It allows to send messages to other windows in the current browser, and receive messages (asynchronously). It works in all modern browsers.

The sender specifies the recipient <window> and domain, and the message as a string. The receiver can see, for each message, from what domain it comes, and take into account only the trusted ones.

How does this help with image loading?

You create an HTML file that will act as a proxy loader, and upload it on S3, next to the images. The proxy’s job is to receive the URL of an image, and return that image serialized as a string.

The application commands, via postMessage: "load /image_path". The proxy, being on the same domain as the image, loads it without any restriction, and replies: "loaded /image_path, here is it, serialized". Now the application can create a new image from the serialization, and the image’s origin will be the application’s domain, not S3.

The good news is, you don’t have to create the proxy or image loading handlers by yourself. I already wrote that code so you can steal my shiny implementation. Check out the live demo to see it in action.

More about the problem

You can include any image, from any domain, on your website with <img>. But when it comes to getting the pixels of an image from another domain – for example, by putting it in a <canvas> and calling getImageData() or getDataUrl(), browsers throw the Security Error because of the Same Origin policy.

This is frustrating if the other domain is also yours. If you could just say, somehow, “this image on S3 is mine, as well as example.com, let the site use the image”.

That is exactly what the Cross-origin resource sharing specification aims to fix. With CORS, the owner of the image grants access to the image to some domains, by setting certain HTTP headers. CORS has decent browser support, but unfortunately S3 does not allow setting the CORS HTTP headers and does not plan to do it either. Update: In September 2012, Amazon added CORS support for S3.

Other solutions

A common solution is to make a proxy on your server, relay all image requests to S3 and return the result. You can do it if you own & configure your server, but for a hobby project hosted on the free plan of Heroku it is not a good fit. When you have one dyno for the entire app, keeping it busy with relaying tens of images per board view is not acceptable.

Another solution is to use a Flash proxy. Enough said.

A third solution involves having a Javascript proxy hosted on S3. If the application is tzigla.com, the S3 bucket would be aliased to images.tzigla.com. The proxy would be loaded in a hidden iframe and it would load the images via Javascript. Then it would declare that its domain is no longer images.tzigla.com, but tzigla.com. After that, it could communicate with all the javascript loaded form tzigla.com and send it the images. The drawback is that after a batch of loading, the proxy cannot be used anymore (because it is not in the same domain as the images anymore).

There is also easyXDM, a library with the same purpose that covers all browsers using a combination of postMessage, Flash, the iframe url hash technique, providing an interface with Socket and RPC abstractions. But if you support modern browsers only, it is too much.

In conclusion

postMessage saves the day and, with a few lines of code, you can get rid of the Same Origin Browser Security error. You can load your images from S3, process then in a <canvas>, and then access the resulting pixels, save the canvas as an image, or whatever else you fancy.

I’ve put the full working code for the demo on github. Let me know if it helps you and, if you have any questions or suggestions, drop me a note to @dira_geek_girl on twitter.

P.S.

This code is extracted from Tzigla, a lovely collaborative art project with tiles and pixel art. You should definitely make some tiles there, it’s pretty fun.