Skip to main content

When it comes to significant changes in website hosting, 2018 might well be known as the year that HTTPS went mainstream. Whereas before hosting a website with an SSL/TLS certificate was only considered something for websites dealing with e-commerce or other personal data, now it also applies to other sites and blogs.
Two major reasons for this are the introduction of free certificates through the Let's Encrypt project, which means the barrier of getting an certificate has been lowered, and the fact that browsers (especially Chrome and Firefox) have started marking non-HTTPS sites as not secure.

Most hosters will nowadays support the adding of SSL/TLS certificates, but if you're hosting a website using Azure Web Apps on the Shared plan you run into a few issues.
The Shared plan does allow you to add a custom domain name, but not a custom certificate for that domain. Microsoft has responded to requests for adding custom HTTPS certificates to that tier by saying the Shared plan is not recommended for production sites. And while that is true for larger sites, it's in my experience perfectly possible to host a small website on the Shared hosting plan.
It would be a fair increase in cost (+470%) to move from the Shared to the Basic plan (which does support custom domains with HTTPS certificates) and that is often not afforable for the smaller sites.
Another argument that Microsoft give is that the Shared tier does have HTTPS support, but only on the azurewebsites.net domain (each web app has its own default domain like https://<myapp>.azurewebsites.net).

But recently I came across Azure Function Proxies and that gave me an idea.
Azure Function Proxies allow you to set up an endpoint (i.e. a URL) that is implemented by another resource (i.e. a different app or URL). Since Azure Functions (which is where the proxy will be hosted) has got support for custom domains with HTTPS, this would allow me to create an endpoint for the website which then calls the actual Azure Web App using the azurewebsites.net domain:

azure-functions-diagram.png

Now, this is certainly not a new idea. Troy Hunt has already been advocating a very similar setup, but instead of using Azure Functions it uses Cloudflare. I know that using Cloudflare has certain other advantages (DDOS protection, CDN for quicker access across the world), but I wanted to try this out since it keeps everything within the same Azure platform.
Some people have had concerns about this type of solution, since the proxy will decrypt the data and then encrypt it again (for the different certificate on the Azure Web App) to send to the backend service (and vice-versa for the response), but I think the risks there are pretty low, especially when it's all hosted within the same provider and data centre.

So let's have a look at how to achieve this setup.
First thing to do is to set up a new Azure Functions proxy. The process for this is described in more detail here, but I'll just share the resulting proxies.json entry:

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "My App Proxy": {
            "matchCondition": {
                "route": "{*route}"
            },
            "backendUri": "https://myapp.azurewebsites.net/{route}",
            "requestOverrides": {
                "backend.request.headers.X-Forwarded-Proto-Original": "request.headers.X-Forwarded-Proto",
                "backend.request.headers.X-Forwarded-Host": "request.headers.Host"
            }
        }
    }
}

As you can see it takes any request and passes it to our backend (in this case the Azure Web App). To let the Web App know what the originating request host was we also add set the X-Forwarded-Host header (the default header for dealing with reverse proxy requests in IIS) with the proxy request host name.
The other header added is X-Forwarded-Proto-Original. This includes the request protocol of the original client request (HTTP or HTTPS) and can be used in the backend to deal with any redirects from HTTP to HTTPS URLs. The reason I had to include it this way is because the Azure Functions Proxy already seems to include the default X-Forwarded-Proto header, but this was always set to HTTPS, even if the original client request came from an http URL.

Then it's time to look at our Azure Web App and put a number of URL Rewrite rules in place (in the web.config file) that deal with our incoming requests:

<rule name="Redirect myapp.azurewebsites.net to myapp.com" patternSyntax="ECMAScript" stopProcessing="true">
    <match url=".*" />
    <conditions>
        <add input="{HTTP_X_FORWARDED_HOST}" pattern="^myapp.com$" negate="true" />
    </conditions>
    <action type="Redirect" url="https://myapp.com/{R:0}" />
</rule>
<rule name="Redirect to HTTPS" patternSyntax="ECMAScript" stopProcessing="true">
    <match url=".*" />
    <conditions>
        <add input="{HTTP_X_FORWARDED_HOST}" pattern="^myapp.com$" />
        <add input="{HTTP_X_FORWARDED_PROTO_ORIGINAL}" pattern="^https$" negate="true" />
    </conditions>
    <action type="Redirect" url="https://myapp.com/{R:0}" />
</rule>
<rule name="Rewrite host header" patternSyntax="ECMAScript">
    <match url="(.*)" />
    <conditions>
        <add input="{HTTP_X_FORWARDED_HOST}" pattern="(.+)" />
    </conditions>
    <serverVariables>
        <set name="HTTP_HOST" value="{C:0}" />
    </serverVariables>
    <action type="None" />
</rule>

The first rule checks the incoming request's X-Forwarded-Host header value (the original client request) and if it's not on our main domain it will return a 301 redirect response, which the proxy passes back to the client and they will get redirected to the right domain.

The second rule checks that the incoming request is on our main domain (using X-Forwarded-Host) but is not called using HTTPS (by checking the value of our X-Forwarded-Proto-Original header). If that's the case it will also return a redirect response to get the client to use the right domain and HTTPS protocol.

And the final rule is to rewrite the request host header with the value of the X-Forwarded-Host header. This is required if your backend app is dealing with the original request domain. So for example, the HttpContext.Current.Request.Url.Host value in your backend will now come from myapp.com and not from myapp.azurewebsites.net.

If you were to add these rulse straight to your web.config, your website will most likely break. This is due to the fact that URL Rewrite by default doesn't allow custom headers or the host header to be set.
In order to fix this we need to add them to the list of allowed server variables. On Azure Web Apps this is not something you can do in the web.config, but instead you need to add an applicationHost transform file as described here. Once that file has been added, the list of allowed variables can be added:

<?xml version="1.0"?> 
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> 
  <system.webServer> 
    <rewrite>
      <allowedServerVariables>
        <add name="X-FORWARDED-PROTO-ORIGINAL" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
        <add name="X-FORWARDED-HOST" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
        <add name="HTTP_HOST" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
      </allowedServerVariables>
    </rewrite>
  </system.webServer>
</configuration>

Restart the web app and this will now be added to the applicationHost.config file of the Azure Web App's IIS instance.

The final step in the process is to update your domain bindings. You want to remove your main domain bindings from the Web App and instead add them to the Azure Functions app that contains your proxy. Then add your SSL/TLS certificate there and add that to the binding as well.
And now your app should be routing all the request via HTTPS and the proxy to your backend app, and return the server responses back to the client via the proxy.

Since Azure Functions proxies are billed based on consumption (i.e. you pay for the duration of the code executing and returning the request), and it includes a very generous free grant of 1 million requests per month, this is a very cost-effective way of hosting a small website with HTTPS on Azure Web Apps.

My experience of setting up Azure CDN on this site and fine-tuning it to automate the deployment of static files during the build process on Visual Studio Team Services.

Read more

I noticed that a website hosted on Azure Web Apps was generating multiple Umbraco trace log files for the same day. I went to investigate what caused it, and also came up with a suggested change to resolve this.

Read more