by Brett Andrew 9th April 2021
How to set host name / client IP when you have multiple sites pointing to a single instance. This includes multiple instances coming in via Azure Front Door and also caters for other urls going to that site directly (not using Azure Front Door) incase you have older sites on the same system that are not going to use Azure Front Door.
By default when you setup a backend for Azure Front door, it uses the default URL that the application service has. This is a problem for Kentico Xperience when you have multiple host names that point to that back end application service.
Initially I thought it would be obvious to use the X-FORWARDED-HOST that Azure Front Door provides, but Xperience is too smart and there is no way to force this domain header, possibly you could do it though a url rewrite, but the problem is if you change the url to be the front door you get into a loop.
The best way to solve it is to have dedicated DNS entries for each of the backend services, this ensures that Xperience knows which site to serve. Don't worry, your clients will never see this domain name, its masked by Azure and you can even lock it down so its only visible inside Azure.
The example below shows two regions to show how you add additional regions for your App services.
So one other thing to remember is that Azure Front door should be used to generate/manage the SSL certificate of the frontend domain, but until a valid SSL certificate is setup it will continue to redirect to the url of the backend App Service, so be patient while this is setup.
Azure Front door can act like a CDN too, so I use it to cache REACT so those users in other countries benefit from the faster loading times.
Xperience will then receive the correct URL and load the correct site and also show the users correct IP address (without that section it shows the front door IP address). You will need the K12/K13 license to be on the backend server (not the front end domain name), use an Alias License key if you have already created the Production license, Alias licenses are free.
The other things you need to do when using Azure front door is to check for SSL (if you use Azure to offload the SSL and the connection between Azure and Xperience is not encrypted) you need to stop Xperience from trying to redirect the browser to the SSL version.
You can also get the users actual IP address too, otherwise the logs show the Azure Front Door IP.
Here is the code to do that here.
These are the Header variables that Azure Front Door sends to Xperience:
X-Forwarded-Proto (will have a value of http or https)
X-Forwarded-For (will have the users IP address)
X-Forwarded-Host (will have the users Host/Domain name that initially came to Azure Front Door)
The original documentation for this is from Kentico
https://docs.xperience.io/securing-websites/deploying-websites-to-a-secure-environment/configuring-ssl/ssl-accelerator-support
I modified it to include the Users IP address.
Save this file as AzureHeaderHandler.cs under the folder App_Code or Old_App_Code (recompile your site).
using System;
using System.Web;
using System.Collections.Specialized;
using CMS;
using CMS.DataEngine;
using CMS.Base;
using CMS.Helpers;
using CMS.EventLog;
// Registers the custom module into the system
[assembly: RegisterModule(typeof(SSLRequestModule))]
public class SSLRequestModule : Module
{
// Module class constructor, the system registers the module under the name "SSLRequests"
public SSLRequestModule()
: base("SSLRequests")
{
}
// Contains initialization code that is executed when the application starts
protected override void OnInit()
{
base.OnInit();
// Assigns a handler called before each request is processed
RequestEvents.Prepare.Execute += HandleSSLRequests;
RequestEvents.Prepare.Execute += HandleUserHostAddress;
// Sets the URL port used for HTTPS requests
URLHelper.SSLUrlPort = 443;
}
// Checks if requests are forwarded as SSL
private static void HandleSSLRequests(object sender, EventArgs e)
{
try {
if ((HttpContext.Current != null) && (HttpContext.Current.Request != null))
{
// Loads the request headers as a collection
NameValueCollection headers = HttpContext.Current.Request.Headers;
// Gets the value from the X-Forwarded-Ssl header
string forwardedSSL = headers.Get("X-Forwarded-Proto");
RequestContext.IsSSL = false;
// Checks if the original request used HTTPS
if (String.Equals(forwardedSSL, "https", StringComparison.OrdinalIgnoreCase))
{
RequestContext.IsSSL = true;
}
}
}
catch (Exception ex)
{
EventLogProvider.LogException("AzureSSLHandler", "ERR", ex);
}
}
/// <summary>
/// Provides Kentico with the correct Host Address if the original address is hidden by using a WAF or Load Balancer or Other Proxy
/// </summary>
private static void HandleUserHostAddress(object sender, EventArgs e)
{
try
{
if (HttpContext.Current != null)
{
// Gets the value from the X-forwarded-for header and pass it to the request context
var xForwardFor = HttpContext.Current.Request.Headers.Get("X-Forwarded-For");
if (!string.IsNullOrEmpty(xForwardFor))
if (xForwardFor.Contains(","))
{
RequestContext.UserHostAddress = xForwardFor.Split(',')[0];
}
else
{
RequestContext.UserHostAddress = xForwardFor;
}
}
}
catch (Exception ex)
{
EventLogProvider.LogException("AzureHostHandler", "ERR", ex);
}
}
}
The one side effect I have not resolved yet, is when you visit your root domain, it redirects to the App Service name, to get around this always send your admins / editors to the site with the url /Admin/cmsadministration.aspx or /admin/
This then keeps the correct front end domain name. You will also notice when you switch between domain names in the Editor it keeps the correct domain name, so its only this initial loading that is the problem. I think this could be solved with a URL rewrite for the root domain, but yet to work it out, if you work it out please let me know!
Powered by mition