Hosting a Single Page App behind Azure Application Gateway Without Breaking Deep Links

Recently I was working with a colleague who’s developing a Single Page Application (using React, but the same would apply to any JavaScript SPA framework). We wanted to host the app as a static website in Azure Blob Storage, as that’s the most cost-effective and low-maintenance option for hosting this type of static content. Publishing would happen through Azure Application Gateway.

SPA’s and deep linking

One of the challenges with single page apps is how to handle deep linking. In other words: if the user starts neatly at http://hostname.com and navigates from there, the SPA framework will handle the routing and ensure that http://hostname.com/path/to/content will trigger the loading of a view. But when a user bookmarks that link and uses it directly, the server will try to look for some file that sits at that location – and fail, because the app is actually contained in that single page (typically index.html).

This was no different for us, so we needed some URL rewriting-like mechanism to ensure that a request to http://hostname.com/path/to/content would re-route to http://hostname.com (or http://hostname.com/index.html). That way, index.html would be served to the user for every request to load the SPA app, which in turn loads the requested view.

Obviously, a static website in Blob Storage doesn’t provide this out of the box, so we considered using Azure CDN which offers some tiers that support URL rewriting options capable of this. But for me, it seemed wrong to require the use of a CDN to do URL rewriting: it negatively affects both the cost-effectiveness and the low-maintenance properties that made us host it in Blob Storage. And, maybe even more important: that’s not what a CDN is for.

Besides, for all our other outward-facing applications, we’re using Azure Application Gateway already anyway, to do load-balancing and firewalling. So I preferred handling this in Application Gateway through path-based routing rules, and a HTTP setting with the ‘Override backend path’ set.

Failed approaches

The first attempt was to simply set the backend path to /index.html, in the hopes that all requests would end up at http://hostname.com/index.html. This didn’t work, however: what this setting does is basically prepending the path override to the requested path. So the full URL would read: http://hostname.com/index.html/path/to/content. And that will not serve up index.html.

On the second attempt, I tried /index.html/# as the override backend path. The resulting URL would be http://hostname.com/index.html/#/path/to/content, and I hoped that this would work, but for some reason that’s still unclear to me, it doesn’t.

Third time’s a charm

The third attempt was a winner however, even though in my mind it’s just a variation on the fragment identifier-approach: once I set it to /index.html?path=, it all started working like a charm. That made sense to me, since the resulting URL would now be http://hostname.com/index.html?path=/path/to/content, i.e. a URL that points to index.html. Again, I fail to see why the URI fragment approach did not work, so if someone can shed some light, please do!

An alternative approach

After all was said and done, however, we landed on a different solution altogether. Having a dependency on Application Gateway is not too big of an issue for us since we’re using it anyway, but having no dependency at all is still preferable. So we simply changed the routing in the app itself to use a fragment identifier. So instead of expecting http://hostname.com/path/to/content, the app now expects http://hostname.com/#/path/to/content. That way, index.html is always being served by default, without URL rewriting and without using the ‘Override backend path’ setting. This may negatively affect indexation by search engines, but since our app sits behind a login anyway, this doesn’t matter for us.

But since search engine indexation may matter to some, I figured I’d share my initial approach anyway, for everyone who has a need to host an SPA behind Application Gateway and retain search engine-friendly deep linking support.

What Exactly Is That CORS-Thing?? The What, the Why and the How Explained

If you’ve stumbled upon this post, chances are you’ve encountered some strange behavior while trying to call an endpoint, like a REST API for example, from within the browser. You may have seen your browser issue an OPTIONS request that is greeted with a 405 Method Not Allowed issued by the API.

fiddler

If this has happened to you, you are probably serving the JavaScript from another application than the one hosting the API.

You were probably expecting an XmlHttpRequest fetching a JSON document instead of that failed OPTIONS request, so read on to find out what’s going on and what you can do to successfully call that API.

So What’s CORS?

In short, you would probably benefit from enabling CORS on your API. CORS is short for Cross Origin Resource Sharing, and enabling CORS is basically a way of allowing your web application to call the API from the client browser, while that API is hosted on a host different than the one your web application is served from. You are not allowed to do that out of the box for security reasons. If you are only interested in actually getting this to work, feel free to skip to the How-part of this post.

OK, so you are interested in a little more background. As said, you can’t call API’s from the browser out of the box if they do not reside on the same host (‘have the same origin’) as the web application. Same origin here means: same URI scheme, hostname and port number. This behavior is enforced by the browser. If a piece of JavaScript attempts to call an API of different origin than its own, the browser will first makes a pre-flight request to the target server to ask whether the server is OK with being called from another origin. Enabling CORS means: instructing the API on how to meaningfully respond to such pre-flight requests. Without CORS enabled, API’s typically respond with the 405 we talked about. Most modern browsers support this pre-flight request, which is a prerequisite for using CORS.

Why CORS?

The ‘security reasons’ behind all this are known as the same-origin policy. According to this principle, resources are isolated from each other on the basis of their origin. So, a piece of script for example can only access other documents in the browser when they share the same origin, and it can only call endpoints on that same origin; all resources from other origins are off-limits.

cross-blocked

Source: http://www.lucadentella.it/en/2013/07/11/javascript-same-origin-policy-e-jsonp/

This makes good sense, because failure to restrict this would mean that a malicious web page that is opened in a user’s browser session would have access to all documents and endpoints for other websites the user is also visiting. Imagine one of these other websites being your personal banking environment, and you probably get why the same-origin policy is kind of a good thing.

But obviously, there are also legitimate use cases for cross-origin API calls. Strategists, visionaries and evangelists preach the API-driven world, in which every company should disclose their processes through API’s to be consumed by clients. Those clients typically will not reside on the same origin, but we do want them to be able to call our API’s.

In recent years, several hacks have been conjured up to bypass the same-origin policy, with JSONP being one of the more prominent ones. I won’t dive into the specifics here; you can read all about it online. The issue with JSONP (apart from some sophisticated exploits) is that, as an API publisher, you open up your API for all origins by definition. And this is where CORS comes in: a controlled way of whitelisting some origins while treating all others according to the same-origin policy. And, as a bonus, the implementation is much easier: CORS is entirely a server-side setting, whereas JSONP requires the client to do part of the heavy lifting.

HowTo CORS

So, on to the actual way of doing this. And this is actually the simplest part: you just need to make sure that the API responds differently to the OPTIONS request. What the browser is actually asking, by means of the Origin header it sends along, is whether the specified origin is allowed to call the API. The API may either not allow this at all (the default), only allow a specific list of origins, or allow all origins. And it communicates this by including a Access-Control-Allow-Origin header to the response to the pre-flight request.

fiddler2

A specific value is indicative of an API that allows this specific origin. Alternatively, an asterisk (*) indicates that all origins are allowed.

For a .NET-based WebAPI, you can use OWIN middleware or the WebAPI CORS package, depending on your application architecture and the requirements. The use of CORS through OWIN middleware is nicely described here, while the CORS package method is detailed over here.

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    }
}

And yes, you can only enable CORS on the API side; not on the caller side. After all, the same origin policy is meant to protect the API from access by malicious websites the user may be visiting.

Hope this helps!