.NET Identity and Containers Behind Load Balancers

In this post, we will take a look at the configuration of .NET applications using the Identity libraries for Azure Entra ID, which are containerised and sit behind load balancers.

Before we get into the detail, first I want to share some background. When you don’t have your app registration in Entra ID properly configured with your redirect URI, then you get a pretty common error AADSTS50011, you can see this error below.

One of the things to note here is that in this example we are accessing through Azure Front Door, the scenario would be the same through any load balancer or device that adds the X-Forwarded-Host header.

In this configuration the container stored in Azure Container Apps is in an environment with a private link, so can only be accessed through the Azure Front Door instance.

So when you hit the address configured in Azure Front Door, either your own custom domain, or the azurefd.net sub-domain, then you will be presented with the error above. You would get the same in Azure Kubernetes Service as well.

What is happening?

So what exactly is causing this issue. Well, in the identity framework within .NET, when the authentication event fires, you are redirected to the host you are currently running from, with the callback path attached on the end. In this case, that path is the container URL.

You can actually argue it’s not really a fault at all, and is working exactly how it is meant to. There isn’t an easy way to tell your running in a container, so you cannot make allowances.

That said, you can make allowances for when your are running behind a load balancer. That is by looking for the X-Forwarded-Host header, and reacting to that to manipulate the redirect URL.

Let’s look at some code to resolve the problem.

TL;DR – Here is the full code to add to your Program.cs file. Note to add below your AddAuthentication block.

// Removed for brevity
builder.Services.AddAuthentication(…);
// Configure load balancer headers for MS Identity
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, o =>
{
o.Events = new OpenIdConnectEvents
{
// Override the redirect URI
OnRedirectToIdentityProvider = (context) =>
{
if (context.Request.Headers.ContainsKey("X-Forwarded-Host"))
{
context.ProtocolMessage.RedirectUri = $"https://{context.Request.Headers["X-Forwarded-Host"]}{builder.Configuration.GetSection("AzureAd").GetValue<string>("CallbackPath")}";
}
return Task.FromResult(0);
}
};
});
// Configure forwarding headers
builder.Services.Configure<ForwardedHeadersOptions>(o =>
{
o.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
o.KnownNetworks.Clear();
o.KnownProxies.Clear();
});
// Below the following line…
var app = builder.Build();
// Add this before app.UseAuthentication()
app.UseForwardedHeaders();
view raw Program.cs hosted with ❤ by GitHub

A quick explanation for those who want to understand more. First of all, we are overriding elements of the OnRedirectToIdentityProvider event, here we are looking for the header X-Forwarded-Host. If found, we are adding the value of that header, and from our configuration, we are appending the callback path.

Next we are clearing any known proxy configurations and setting the forwarded headers which will be processed.

Then finally, after the bulder.Build() call, before you call your authentication package, make sure you add app.UseForwardedHeaders(). This completes the solution, now deploy your container and it should work as expected.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.