Modifying host headers with Azure websites when using it behind an Application Gateway or reverse proxy via URL Rewrite Module

I'm currently using Azure's Application Gateway with a backend pool utilising Azure's App Service. When the application gateway forwards your request to the backpool, it also forwards X-Original-Host HTTP Header together with it to help you identity which listener the gateway originally acted upon but what if you wanted it to match the HOST header at the web server (app service)? This might be useful for cases where you need to generate absolute URLs in your web app for whatever reason be it sitemaps or some syndication feed etc. Since we are using Azure's App service, we don't have as much control over IIS as we are used to but luckily for us Azure has provided us with a way to transform the applicationHost.config via transforms.

On the Azure Portal head over to your app service then jump over to the Kudu > Debug console > CMD and create a file name applicationHost.xdt under d:\home\site directory which allows you set the allowed server variables for IIS by overriding/transforming the applicationHost.config file

applicationHost.xdt

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

Then on Web.config file within d:\home\site\wwwroot you'll need a new rule which basically sets the HTTP_HOST server variable to be {HTTP_X_ORIGINAL_HOST} (surrounding curly brace means to evaluate) on the condition that the value of the http header X-Original-Host is NOT empty (e.g. ^$).

X-Original-Host is a HTTP header which Azure's Application Gateway forwards to its backend pools

Web.config

<configuration> 
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Set HOST header based on X-Original-Host header">
          <match url="(.*)"></match>
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
            <add input="{HTTP_X_ORIGINAL_HOST}" pattern="^$" negate="true" />
          </conditions>
          <serverVariables>
              <set name="HTTP_HOST" value="{HTTP_X_ORIGINAL_HOST}"></set>
          </serverVariables>
        </rule>
      </rules>      
     </rewrite>
  </system.webServer>
</configuration>

IMPORTANT : Once the two changes are made, we will need to restart BOTH the SCM site (via the Restart Site button on {foo}.scm.azurewebsites.net/SiteExtensions) and also the website itself (Via the Azure Portal, CLI or Powershell) or the changes won't kick in.

And to test whether the correct headers are being transformed, simply add some debugging code in your html (or razor page in my case of ASP.NET MVC)

Index.cshtml

<div class="jumbotron">    
    <p>HTTP_HOST : @Request.ServerVariables["HTTP_HOST"]</p>
    <p>HTTP_X_ORIGINAL_HOST : @Request.ServerVariables["HTTP_X_ORIGINAL_HOST"]</p>   
</div>

<div class="jumbotron">
    <p>X-Forwarded-For : @Request.Headers["X-Forwarded-For"]</p>    
    <p>X-Forwarded-Proto : @Request.Headers["X-Forwarded-Proto"]</p>
    <p>X-Original-Host : @Request.Headers["X-Original-Host"]</p>    
 </div>

Side note for deployments outside of App Service

If we have greater control over the hosting environment, perhaps using App Service for Windows Containers (in Public preview at time of writing) the same could be achieved using powershell with Set-WebConfigurationProperty cmdlet in your Dockerfile while building your image. The below example assumes you've built your image using the Default Web Site but can obviously be changed to however your website is configured.

Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -location 'Default Web Site' ` 
-filter 'system.webServer/rewrite/allowedServerVariables' -Name '.'  `
-value ( @{ name = 'HTTP_HOST' }, @{ name = 'HTTP_X_ORIGINAL_HOST' } )

Which would produce

applicationHost.config

<configuration>
    <location path="Default Web Site" overrideMode="Allow">
            <system.webServer>
                <rewrite>
                    <allowedServerVariables>
                        <add name="HTTP_X_ORIGINAL_HOST" />
                        <add name="HTTP_HOST"/>
                    </allowedServerVariables>
                </rewrite>
                <asp />
            </system.webServer>
    </location>
</configuration>

Here is the Dockerfile for completeness

Dockerfile


ARG LTSC_VERSION=ltsc2016
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.7.2-windowsservercore-${LTSC_VERSION}

RUN iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
RUN choco install urlrewrite -y

WORKDIR /inetpub/wwwroot 

RUN Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -location 'Default Web Site' -filter 'system.webServer/rewrite/allowedServerVariables' -Name '.'  -value ( @{ name = 'HTTP_HOST' }, @{ name = 'HTTP_X_ORIGINAL_HOST' } )

ARG PUBLISH_DIR
ADD output/${PUBLISH_DIR}/. ./

References

https://github.com/projectkudu/kudu/wiki/Azure-Site-Extensions#understanding-what-could-go-wrong-with-xdt-transforms