CORS: The Internet’s security “bouncer”

One of the realities of the modern web is that every new technology needs to balance between functionality and security.  In this article, we talk about one particular case of this balance that comes into play and how it may affect working with virtual reality technology on the web.

When building a website, it’s not unusual to embed resources from one website inside another website.  For example, an image on a webpage (loaded via the “img” tag) can point to a JPEG stored on an entirely different server.  Similarly, Javascript files or other resources might come from remote servers.  This introduces some potential for security issues for consumers of web content.  For example, if your website loads a Javascript file from another site, and a hacker is able to modify that file, you’ll be loading potentially malicious code on your website.  Browsers normally address the dangers of mixed-origin content by tightly controlling the ways in which scripts from different servers can talk to other servers on the Internet.  This area of security is called “cross origin protection.”

For example, let’s say we have a webpage, Foo.com.  This webpage loads in our browser, along with an associated Javascript file, Foo.js, which is responsible for loading additional assets and managing interactive content on the page.  This Foo.js file, then, attempts to load some image content from Bar.com and plop it into a <canvas> tag on our webpage.  This all seems innocent enough so far, right?… WRONG!

In fact, this is a major security risk.  For example, imagine Bar.com is a web server that displays scanned documents.  For illustrative purposes, let’s pretend Bar.com is actually “IRS.com”, and contains millions of users’ scanned tax records.  In the scenario above, without any security measures in place, our Foo.js file would be able to reach into Bar.com, grab a secret file, plop it into the page’s <canvas> tag, read out the contents, and store it back to the Foo.com server for further exploitation.  The server administrator at Foo.com would then have access to millions of users’ tax records data that had been maliciously sniped from Bar.com.  It’s easy to see, then, that scripts like Foo.js can quickly become a security risk.  Content that “crosses origins”–that loads from separate places on the Internet–needs to prevented, from co-mingling in potentially malicious ways.

The solution?  Browsers, by default, will block this type of cross-origin content loading altogether!  If your website and script are being loaded from Foo.com, then your browser forces the website to “stick to its lane”.  Foo.com webpages will only be allowed to load other content that comes from Foo.com, and will be blocked from loading–and potentially exploiting–content from Bar.com.

Cross-origin protection and WebVR

This basic situation plays out in many different ways within web technology.  A cross-origin image can’t be read back into an HTML <canvas>, and, importantly for our conversation, can’t be used as a WebGL texture.  WebGL is the technology underlying all of the web-based virtual reality tools like AFrame and ThreeJS.  The specifics of why cross-domain images can’t be used as textures in WebGL are pretty fascinating, but also pretty complicated.

In practice, what this means is that if your virtual reality Javascript is stored on one server, it can’t easily load images or videos stored on another server.  Unfortunately, this could be pretty restrictive in when trying to create WebVR content; even within the University, we often have resources split across many servers.

Cross-Origin Resource Sharing (CORS)

Fortunately, there’s a solution called Cross Origin Resource Sharing.  This is a way to tell your web servers to explicitly opt-in to cross-domain uses of their content.  It allows a webserver like Bar.com to say “I expect to send resources to scripts at Foo.com, so allow those requests to go through and load into the browser.”  It’s basically the Internet equivalent of telling the bouncer at your favorite club to put your buddy on a VIP access list, rather than leaving him standing at the door.  As long as the bouncer…erm, browser…sees that a specific source of data is vouched for, it will allow the requests to go through.

whiteboard drawing of a browser requesting content from bar.com and getting blocked by stick-figure CORS bouncer guy

Doing these CORS checks requires some extra communication between the browser and the server, so occasionally the browser skips CORS checks.  However, when creating VR content in particular, sometimes we want to explicitly ask the browser to perform a CORS check so that the content can be loaded into a secure element like an HTML <canvas> or WebGL texture for VR display.   In this case, the “crossdomain” attribute on HTML elements is necessary.  If we load an image using the HTML code <img src="http://bar.com/image.jpg" crossorigin="anonymous"/> the browser will perform a CORS check before loading the image for the user to view.  Assuming the server hosting the image (in this case, Bar.com) has CORS allowed for the target website (Foo.com), that image will be considered safe for things like loading into an HTML <canvas> or using as a WebGL texture on Foo.com.  In this way, VR websites hosted on one server can continue to load pre-approved resources from other remote servers, as long as those servers provide the necessary “OK” for CORS.

CORS troubleshooting

Even if you’re doing everything right in the browser and on the server, CORS can still provide some headaches.  When you encounter a CORS-related failure, the errors that are generated are often opaque and hard to unpack.  Things like caching within the browser can also make these errors feel sporadic and harder to track down: one minute it may look like your image is suddenly loading (or suddenly not loading) correctly, when in fact what you’re seeing is a previously-cached, older version of the image or associated Javascript that your browser has stored behind the scenes.

Even worse, some browsers have broken or unreliable implementations of CORS.  For example, Safari on Mac OS X cannot currently load videos via CORS, regardless of the browser and server settings.  As a general tip, if you see errors in your browser’s web developer console that mention any sort of security restriction, start by looking into whether you’ve come up against a CORS-related issue.