Umbraco Multi-Site with Azure Front Door

How to access multiple homes via one Front Door

Problem

An Umbraco website configured for multi-site was unable to direct traffic to the correct root node. The architecture involved routing traffic through Azure Front Door to a single Windows based Azure App Service instance, configured with an additional staging slot. The Culture and Hostnames properties were configured for each node to have unique domains against each.

Diagnosis

With most reverse proxy solutions when a web request is forwarded on to receiving web server the origin Host header is typically hidden, this is due to the Host header for the web request being the one the reverse proxy solution knows for the intended endpoint. This is no different for Azure Front Door, as the documentation states, the original client requests expected hostname is placed into the X-Forwarded-Host header in the subsequent request to the backend.

Since this value is obscured, Umbraco is unable to route the traffic to the expected endpoint and selects the default route.

Expected Solution

Since the project was using Umbraco 10, our solution needed to target how ASP .NET Core handles headers. It is possible to configure an ASP .NET Core site to replace the Host header with the value in X-Forwarded-Host using the included ForwardHeadersMiddleware.

app.UseForwardedHeaders();

Which must be configured when the services are being built.

services.Configure<ForwardedHeadersOptions>(options => options.ForwardedHeaders = ForwardedHeaders.All);

This was tested locally using Postman to send a request against our running server along with the X-Forwarded-Host header set to being the requested value.

Unexpected Problem

When the solution was deployed into Azure, when visiting each distinct host the site was still loading the default route. It appeared that the expected solution did not work. This was further diagnosed with the incoming request headers being dumped to the client for debugging.

It was clear that the Host was not being replaced with the value in X-Forwarded-Host as it was when running locally. The question is what was different?

Further Diagnosis

The way that Azure hosts ASP .NET Core sites, specifically when using a Windows based App Service, the sites hosting model can be configured to run InProcess or OutOfProcess.

When running in InProcess IIS is repsonsible for routing requests and controls the way that headers are processed in the HTTP request.

With OutOfProcess the site is launched using the dotnet command and exists as a sub-process with HTTP traffic being sent to the launched Kestrel web server.

In our instance, we were hosting ASP .NET Core InProcess negating the work that the ForwardedHeadersMiddleware was doing.

Actual Solution

Our approach needed an update to the web.config for IIS to rewrite the Host header with the X-Forwarded-Host.

<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Replace Host with X-Forwarded-Host Header" enabled="true" stopProcessing="false">
          <match url="(.*)" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
            <add input="{HTTP_X_FORWARDED_HOST}" pattern="^$" negate="true" />
          </conditions>
          <serverVariables>
            <set name="HTTP_HOST" value="{HTTP_X_FORWARDED_HOST}" />
          </serverVariables>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

However, when running with the deployed version we will encounter a HTTP 500 Internal Server Error due to the web.config definition being invalid. The config is invalid due to permissions not being granted to the IIS site to rewrite the header server variables.

The permissions can be configured by creating an applicationHost.xdt file with the required XML transformations to allow our web.config rewrite configurations to be valid.

This file needs to be placed into the the /site/ folder (one level up from the wwwroot folder) which can be done through the Kudu console.

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.webServer>
    <rewrite>
      <allowedServerVariables>
        <add name="HTTP_HOST" xdt:Transform="InsertIfMissing" />
        <add name="HTTP_X_FORWARDED_HOST" xdt:Transform="InsertIfMissing" />
      </allowedServerVariables>
    </rewrite>
  </system.webServer>
</configuration>

Additional Note

When using Azure App Service with multiple Deployment Slots be sure to place the applicationHost.xdt into each slot!

© 2024 • af5e6bb