C#, .Net and Azure

Serve html via Azure Functions

2020/06/13

Yet another quick post about Azure Functions. Sourcecode can be found here.


Recently I had the need to distribute a small website. If it were just html I would have used Azure Storage and CDN like my blog does.

However, I had two additional requirements that made that unsuitable:

  1. I needed to pull dynamic data from a backend - solved via an azure function
  2. The site should be protected (only authorized users should be able to access it) - solved via App Service Auth

I could have mixed Azure functions and CDN or used the free app service tier since its 60min/day limit would have been enough for my usecase.

If you look at my blog post setting up Blob Storage and CDN there is quite a lot to be done to get it running and while I definitely recommend the CDN approach for enterprise but for a quick private website it is quite some hassle (and wouldn’t work in my case because there is no auth for CDN).

Since all my other projects are also based on Azure function I decided to just rely on them here as well instead of setting up an app service plan.

The project is a basic Azure function with two HTTP proxies.

Proxies allow rewriting the incoming requests.

The apiProxy rewrites all /api/ calls to the correct api endpoint and the httpPproxy serves content from storage by calling a small function that loads files dynamically from blob:

{
  "$schema": "http://json.schemastore.org/proxies",
  "proxies": {
    "httpProxy": {
      "matchCondition": {
        "methods": [ "GET", "HEAD", "OPTIONS" ],
        "route": "{*path}"
      },
      "backendUri": "http://localhost/api/http?path={path}"
    },
    "apiProxy": {
      "matchCondition": {
        "route": "/api/{*url}",
        "methods": [
          "GET",
          "PUT",
          "POST",
          "DELETE",
          "HEAD",
          "OPTIONS"
        ]
      },
      "backendUri": "https://localhost/api/{url}"
    }
  }
}

Any request to the azure function is now converted to an api call of the http function unless the requests starts with /api/ in which case it is redirected to an api endpoint.

Now I can add as many api endpoints as I want and they will be accessible just fine.

The http function on the other hand simply looks for a file in blob storage and serves it to the user.

So far this has only minor advantages over serving the html file directly from storage (e.g. via a CDN) (namely: I don’t need to setup CDN and I don’t run into CORS restrictions).

However with this setup I am now also able to make use of App Service Auth which takes just a few clicks to set up authorization for any common OAuth provider (AAD, MSA, Facebook, Google, Twitter, ..).

I decided to use AAD auth because it gives the most fine-grained control.

By enabling “user assignment required” on the enterprise app I am now able to granularly decided which user is allowed to access the page.

user assignment required

Furthermore I added a custom domain (CNAME binding & let’s encrypt certificate via lets-encrypt-azure) and now users don’t even know it’s an azure function!

This github issue also has some interesting pointers as one user even shows how to directly fetch content from a blob, skipping the need for the http function I wrote.

It’s certainly something I’ll look into in the future as my function is currently hardcoded to return text/html content only.


As always I also posted the solution on github.