Hosting an Angular App as a static website with Azure Function Proxies
Tags: angular, azure, proxies, staticwebsite
Published: 2020-01-21 09:28:10

Azure static websites are a great way to host your web applications. Static websites are much [cheaper and faster](https://medium.com/medialesson/best-way-to-host-a-single-page-application-spa-in-microsoft-azure-3e70cbd075c3) than app services. This makes them perfect for simple websites with static information, but you will run into two problems when trying to host an Angular app.

###Problem 1: Custom headers###
One limitation of static websites is the inability to add custom headers to requests. Being able to add security headers such as CSP is an essential feature for modern websites. Although this feature is highly requested, it’s not available yet. You can upvote it on the [Azure feedback forum](https://feedback.azure.com/forums/217298-storage/suggestions/34959124-allow-adding-headers-to-static-website-hosting-in) if you want to see this implemented!

###Problem 2: Angular Routing###

Angular apps are a great fit for static websites, because routing happens on the client. However, a problem occurs when making a request to a ‘deep link’, such as `https://mysite.com/heroes/42`. The request will return a 404 because it can not find the resource on the server. You can configure a static website to redirect failed requests to the `index.html`, but the 404 will still show up in your network tab. This becomes a problem when using monitoring services such as Application Insights, or when writing integration tests with Selenium or Cypress.

In this post I will describe how these problems can be overcome with the help of Azure Function proxies.

---

##Setting up a static website##

Setting up a static website in Azure is very simple. Create an Azure storage account and enable the static website setting. You can find a step by step guide in the [Azure documentation](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website-host).

![Static website screenshot](https://miro.medium.com/max/2348/1*KGOMrBP4Bnn1UMZZkWaJcQ.png)

Enabling the static website setting will create a container called `$web` in the blob storage. This is where you can upload static HTML, CSS, and Javascript files. In the case of an Angular app, this is where you would put the contents of your dist folder. To try it out, you can use the classic Angular Tour of Heroes app, which you can find on Github:

https://github.com/johnpapa/angular-tour-of-heroes

Clone the repository, build the app and copy the contents of the generated dist folder to the `$web` container in the storage account. Of course you can also use your own Angular app with routing.

When you enter the primary endpoint of your static website in your browser (e.g. `https://tourofheroes.z6.web.core.windows.net`) you will see that your app is up and running!

A useful tool to find out if you’re missing any security headers on a website is https://securityheaders.com/. Let’s scan our static website and see how we’re doing!

![Security report summary gives an F rating](https://miro.medium.com/max/1915/1*Yopqt4eI87vw8WxfqC4-vw.png)

Ouch, that doesn’t look good… Unfortunately it’s not possible to add custom headers to a static website in Azure (yet). This is where Azure Functions Proxies come in.

##Adding custom headers##
Azure Functions Proxies were introduced as a tool for serverless API management. They allow you to modify requests and responses of your APIs. We’re going to use them to add custom headers to requests to our static website, and to make requests to deep links work.

Start by [creating a new Function App](https://docs.microsoft.com/nl-nl/azure/azure-functions/functions-create-first-azure-function#create-a-function-app) in the Azure portal. Make sure to select the consumption plan to keep costs down. If you want to, you can select the same storage account as your static website. We’re only interested in adding proxies so there is no need to create functions inside the app.

Once you’ve created the Function App, you are ready to start configuring proxy settings. Start by creating the first proxy named `root`. This proxy will route the Function App URL to the `index.html` of the static website. In the Route template field, enter ‘/’. Select the GET and HEAD headers in the Allowed HTTP methods. In the Backend URL field, enter the URL of your static website.

The Response override section is where you can add custom headers! Let’s add the following security headers as recommended by securityheaders.com:

| Header | Value | 
| ------------- |:-------------:| 
| Strict-Transport-Security    | max-age=31536000; includeSubDomains |
| X-Content-Type-Options      | nosniff |  
| X-XSS-Protection | 1; mode=block      |   
| x-frame-options | SAMEORIGIN |
| Content-Security-Policy | default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; |
| Referrer-Policy | same-origin |
| Feature-Policy | payment 'self'; geolocation 'self'; |

Once you’re done, your proxy should look something like this:

![Function proxies](https://miro.medium.com/max/1024/1*xKh7sbudCpJYEx4U8cQ3Eg.png)

Requests to the URL of the Azure function proxy will now be routed to the static website URL, with the added security headers! Entering the proxy URL in your browser will load the Angular app you uploaded to the static website. You will also notice that requests to the Javascript files needed to run the Angular app (e.g. `https://tour-of-heroes.azurewebsites.net/main.js`) return 404 errors. This is because it will look for the file on the Azure Function website. We need to create another proxy that routes the request to the right file on the static website. Add another proxy called ‘files’ with the matching condition `/{filename}.{ext}` that routes to `https://tourofheroes.z6.web.core.windows.net/{filename}/{ext}`.

![files proxy](https://miro.medium.com/max/774/1*lx0RY1e9LxpfkJF5AfMgJQ.png)

In case your Angular app uses assets, add another similar proxy. It should use the matching condition `/assets/{file}` to route requests to `https://tourofheroes.z6.web.core.windows.net/assets/{file}`.

When visiting your site now, using the proxy URL, it should be able to find the Javascript files and load the full Angular app. Scan the proxy URL on securityheaders.com and you will see a result that’s a lot better!

![Security header scan result A](https://miro.medium.com/max/767/1*feERMiSsbZfdl7F7YYPR2Q.png)

##Making requests to Angular routes work##

If your app uses Angular routing you still have work to do. Navigating to a route starting from the root of the app causes no issues, because the Angular router interprets the URL and routes to that page. But entering a deep link in the browser address bar or refreshing the page while on a route does. An example of a deep link is something like `https://tour-of-heroes.azurewebsites.net/detail/14`. The browser will make a direct request to the server for that URL, bypassing the Angular router, and return a 404.

You’ll need to configure the server to route those requests to the `index.html`. With a static website, you can solve this with another proxy.

Use the matching condition `/{*restOfPath}` to route all the requests to `https://tourofheroes.z6.web.core.windows.net/index.html`. You can copy the response overrides from the root proxy using the UI, or use the advanced editor in the top bar to edit the `proxies.json` file.

![routes proxy](https://miro.medium.com/max/778/1*cVXeeCbOi1cWJGZrzzgwIQ.png)

Now you should be able to go straight to any Angular route by entering it in the browser address bar without encountering 404s!

Below you can find an example of a complete `proxies.json` file for an Angular web app:

{% gist https://gist.github.com/Royhoey/93a67eb656ff81346396128e7532a5c0 file=proxies.json %}

The next possible step is to configure SSL and custom domains. You can do this in the Azure Function app itself or by creating an [Azure Front Door](https://docs.microsoft.com/en-us/azure/frontdoor/).

##Cost breakdown##

Since one of the advantages of using static websites is how cheap they are, we don’t want this solution to ramp up the costs.

A request to an Azure Function proxy is billed as one execution. Consumption plan has a monthly free grant of 1 million executions, after that it will cost you €0,169 per million executions. There is also a charge for execution time, measured in gigabyte seconds (GB-s), rounded up to the nearest 128 MB. Memory used by a proxy is less than 128 MB.

This means that 1 million requests will cost you nothing(!), and 10 million requests will cost you €1,61. Get an estimate for your use case with the [Azure pricing calculator](https://azure.microsoft.com/nl-nl/pricing/calculator/).

##Other considerations##

An alternative solution is using the rule engine in Azure CDN to add custom headers and re-write requests. You have to pick the Premium Verizon tier to be able to use it. The first 10 TB per month costs you €0,1333 per GB for requests in Zone 1 (Europe / US). I’m not sure how to compare this to the Azure Function costs, but the possibility of not having to pay anything for a month on a consumption plan appeals to me!

What I like about Azure Function proxies is that changes made in the `proxies.json` will apply immediately. New routing rules on Azure CDN can take up to a couple of hours to take effect, which can be quite annoying when setting it up.

Another thing to keep in mind is the cold start of Azure Functions. Because of dynamic scaling, apps that haven’t executed in a while take longer to start up. There are workarounds for this though, like creating a timer triggered function that runs every couple of minutes. Or, if you are using Front Door, you can create a HTTP-triggered function for health checks to keep the app warm.

---

##Wrapping up##
By using Azure Function proxies you can overcome important limitations of static websites. Use them to add security headers and make requests to routes of your Angular app work!

This way you can enjoy the low costs and high performance of a static website, without compromising on security.

Happy hosting!