Azure static websites are a great way to host your web applications. Static websites are much cheaper and faster 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 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.
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!
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 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:
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}
.
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!
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.
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:
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.
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.
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!
Top comments (1)
Great article! I’m now in the phase of trying to find the best approach of hosting an angular app on Azure storage. At the moment I’m using Azure CDN in front of the storage account to handle rewrite rules, enforcing https and enabling caching an http/2. This works great, but still missing the security headers.
Also struggling with the best approach to add ip ruling to my non-prod CDN endpoints to make them only accessible “internally”. On storage account level you can configure ACL to allow/block certain IP’s. But this is not possible on CDN endpoint leven. Any ideas?