Unity, HTML5, CORS, and You
Due to the fact that Unity is removing support for the Web Player in their 5.4 release, many developers are ramping up on publishing their content using the HTML5 target to ensure forward compatibility. If you do not have any experience with creating Unity HTML5 content, you can view our blog post about the subject to get a brief overview and some tips on uploading your game to Kongregate.
One common issue that tends to trip developers up is the fact that HTTP requests (using the Unity WWW class, for example) may suddenly stop working once they are executed inside the browser context. This can be incredibly frustrating, as there may not be any indication of what is going wrong unless you are closely watching the JavaScript console or network activity tab.
What is CORS, and why should I care?
CORS stands for “Cross-Origin Resource Sharing,” and you can read details about it here. Basically, when configured properly, CORS allows you to securely access resources (such as fonts and API endpoints) across domains using AJAX or other methods.
The reason this is important for your game is that Unity implements the WWW
class using run-of-the-mill AJAX HTTP requests behind the scenes for HTML5 content. This means that if your game needs to access resources on a domain other than the one it is hosted on, the browser will refuse to process the request unless CORS is configured properly. This is a common problem, as game files are generally served from a CDN, while API requests are handled by an application server.
You can generally tell if you are encountering a CORS-related issue by watching the JavaScript error console in your browser and looking for messages such as No 'Access-Control-Allow-Origin' header is present on the requested resource
or Cross-Origin Request Blocked
.
Well, this sucks. I just want my game to work
Luckily, for the vast majority of cases CORS is quite easy to set up. It generally just requires a few small tweaks to your server code or framework. To determine how to set up CORS for your back-end, you can visit an absolutely fantastic website called enable-cors that has detailed instructions for almost every conceivable configuration.
For most cases, just following the basic instructions on enable-cors.org will be enough to get you up and running. This generally involves setting up an Access-Control-Allow-Origin
header to allow your game to access the proper endpoints. However, if you have a more complicated client/server setup, there are some potentially tricky issues that we’d like to cover in more detail.
Losing your head(ers)
We recently helped a game developer diagnose a problem with the HTML5 version of their game where at first glance CORS appeared to be configured properly. Watching the network tab in the browser, we could see the Access-Control-Allow-Origin: *
header coming through, and the request appeared to complete successfully. However, the game was not properly processing the request, and there were no errors in the browser console.
In order to diagnose the problem, we first wanted to verify our hunch that this was a CORS-related issue. The Web Player version of the game worked fine, and starting Chrome with the --disable-web-security
flag (which disables CORS checks) made the issue with the HTML5 version go away, so it seemed we had found our smoking gun. The problem was, the browser appeared to be happily accepting and processing the request, so what could be wrong?
Looking more closely at the Network tab in the Chrome console, we noticed that the responses from the server contained a custom header, similar to the following:
Access-Control-Allow-Origin: game12345.konggames.com
Content-Length: 2168
Content-Type: application/json
X-App-Signature: 0a2fff3c557e7560647ee43a61511d3eb73e23806c6a8fc3951189e82fe86e64
In their C# code, they were checking the validity of the request using the signature using the responseHeaders
attribute on the WWW
object, for example:
var signature = www.responseHeaders["X-App-Signature"]
if(SomeCustomValidator(www.text, signature)) {
// Process response
}
After some additional debugging, we noticed that the custom header was missing from the response! This is where the fine-grained access controls that CORS provides can bite you. By default, CORS does not allow all headers to be passed along to the client. In fact, the only headers exposed by default are:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
Looking at the documentation, we saw that CORS provides an Access-Control-Expose-Headers
directive that allows exactly what the developer was trying to accomplish. The issue was easily resolved by configuring their server to add the following CORS header to responses:
Access-Control-Expose-Headers: X-App-Signature
You can see how this might affect you even if you’re not using custom headers. You may be looking for Content-Length
, Status
, or some other basic header that you expect to be present, and end up pulling your hair out trying to figure out why you can’t access it.
TL;DR
CORS is a very powerful tool that is trivial to enable for most applications. However, if your game has requirements beyond the most basic configuration (such as using custom headers), you may need to spend some time reading the spec and looking over options to determine why your data is not showing up as expected. Also, Chrome’s --disable-web-security
flag can be a helpful tool for verifying that an issue is related to CORS, so don’t be afraid to use it if you suspect something funny is going on.
Good luck, and keep up the great work creating those plugin-free HTML5 games!