X-Forwarded-Proto
Header auzuwerten,
um korrekte OIDC Return-URLs zu generieren und Anwendungsfälle wie HTTP-zu-HTTPS-Weiterleitungen korrekt zu handhaben.Azure Container Apps ist eine Plattform, um einfach Container zu deployen. Darunterliegend wird von Microsoft derzeit ein komplett verwaltetes Kubernetes verwendet. Davon bekommt man als Kund:in der Plattform nur wenig mit – das ist gewollt und meistens auch gut so.
Werden nun ASP.NET Core Applikationen als Container auf Azure Container Apps gehostet, müssen einige Besonderheiten bedacht werden. Dies kommt daher, dass Requests nicht mehr direkt vom Browser zur ASP.NET Core Applikation gelangen. Dieser Umstand muss unter anderem beachtet werden, wenn OIDC als Authentifizierungsprotokoll eingebunden wird.
Für Autorisierungen («Login») mit OIDC müssen Kund:innen an den OIDC-Provider weitergeleitet werden. Dabei muss die ASP.NET Core Applikation dem OIDC-Provider eine Return-URL mitteilen. Auf diese URL wird der Browser vom OIDC-Provider weitergeleitet, sobald sich die Person beim OIDC-Provider erfolgreich authentifiziert hat. Der URL werden Parameter angehängt, womit die ASP.NET Core Applikation die Identität der Kund:innen feststellen kann.
ASP.NET Core, bzw. der entsprechende Code der Microsoft Identity Platform, setzen diese OIDC Return-URL aus folgenden Angaben zusammen:
https
.Host
-Header aus der HTTP(S) Anfrage, bspw. meine-app.com
./auth/success
.Mit den Beispielangaben würde die OIDC Return-URL also zu https://meine-app.com/auth/success
zusammengesetzt.
Dies funktioniert ohne Probleme,
solange die Anfragen von einem Browser direkt zur ASP.NET Core Applikation gelangen.
Heute sind aber Setups von geschäftskritischen Applikationen typischerweise komplizierter. Anfragen gelangen vom Browser nicht mehr direkt zur ASP.NET Core Applikation. Das hat einerseits mit Sicherheitsbedenken zu tun. Es hat aber auch praktische Gründe. Ein CDN kann etwa die Zeit zum Largest Contentfull Paint merklich verringern, aber auch DDoS Angriffe effektiv abwehren.
Aber auch moderne Hosting-Platformen wie Azure Container Apps oder Kubernetes fangen Abfragen des Browsers zuerst ab und leiten diese intern weiter. Dies erlaubt etwa Rolling Deployments, wobei eine neue Version einer Applikation immer zunächst nur einem kleinen Kreis von Kund:innen zur Verfügung gestellt wird, bspw. 10 %. Erst mit der Zeit – und wenn keine Probleme auftreten – werden weitere Kund:innen auf die neue Version geführt.
Eine typische Architektur des Autors besteht aus der Kombination der folgenden Dienste:
Ein Request vom Browser zur Applikation passiert also mehrere Dienste:
All diese Dienste handeln als Reverse-Proxy. Das heisst, die nehmen die Anfrage des vorherigen Dienstes entgegen und starten ihrerseits eine neue Anfrage an den nachfolgenden Dienst. Ohne weitere Massnahme gehen so wichtige Informationen für die ASP.NET Core Applikation verloren, nämlich die ursprüngliche IP-Adresse des Browsers und auch mit welchem Protokoll (HTTP oder HTTPS) die ursprüngliche Verbindung durchgeführt wurde.
Denn die ASP.NET Core Applikation wird jeweils nur vom Azure App Container Ingress kontaktiert und spricht nie mit dem eigentlichen Browser. In unserem Setup spricht auch der Azure App Container Ingress nicht mit dem tatsächlichen Browser, sondern nur mit Cloudflare. Nur Cloudflare kennt die ursprüngliche IP des Browsers und weiss, welches Protokoll der Browser und Cloudflare miteinander sprechen.
Um damit umzugehen, hat sich ein De-Facto Standard herausgebildet.
Die meisten Reverse-Proxies fügen jeweils ein X-Forwarded-For
und ein X-Forwarded-Proto
Header hinzu,
wenn sie eine Verbindung zum nächsten Service starten.
(Bei der Recherche hat der Autor vom neuen und in RFC-7239 standardisierten Forwarded
Header gelesen.
Dieser scheint jedoch erst eine geringe Verbreitung zu haben und wir in der Folge in diesem Artikel ignoriert.)
Beispielhaft funktioniert das so:
https://meine-app.com
.https://meineapp.generiertername-abc1234.westeurope.azurecontainerapps.io
weiterleiten muss.
Dabei fügt Cloudflare der Anfrage unter anderem die beiden HTTP Header X-Forwarded-For: 123.1.2.3
und X-Forwarded-Proto: http
hinzu.
Die IP, mit der Cloudflare diese IP macht, ist in diesem Beispiel 104.22.33.123
.X-Forwarded-For
,
nämlich zu X-Forwarded-For: 123.1.2.3, 104.22.33.123
.
(Theoretisch sollte der Ingress auch einen Eintrag zum X-Forwarded-Proto
Header hinzufügen,
sodass dieser zu X-Forwarded-Proto: https, https
wird.
Das geschieht in der Praxis jedoch leider nicht.
Hier könnte Microsoft nachbessern.)
Der Ingress Service sendet diese Anfrage über eine Azure App Container-interne IP-Adresse,
in diesem Beispiel ist dies die IP 172.70.123.45
.Zuletzt nimmt nun die ASP.NET Core Applikation die Anfrage entgegen.
Die Applikation nimmt die Anfrage über den Port 80 entgegen – also ohne HTTPS.
(Denn sie kann ohne Weiteres kein gültiges Zertifikat für die interne IP erhalten, womit HTTPS wegfällt.)
Ohne die zusätzlich Header wüsste die ASP.NET Core Applikation jetzt also nur,
dass sie über HTTP eine Anfrage von 127.70.123.45
über das HTTP Protokoll erhalten hat.
Sie hätte keine Information darüber,
dass dies nicht der eigentliche Browser ist und der eigentliche Browser mit seinem Gegenüber über HTTPS kommuniziert hat.
Da der Request vom Azure Container Apps Ingress an den ASP.NET Core Container per HTTP zugestellt wird,
würde ohne die X-Forwarded-*
Header die OIDC Return-URL aus folgenden Angaben zusammengesetzt:
http
.Host
-Header, bspw. meine-app.com
./auth/success
.In diesem Beispiel würde die OIDC Return-URL also http://meine-app.com/auth/success
heissen,
obwohl der Browser die Webseite tatsächlich mit https://
aufgerufen hat.
Die meisten OIDC-Provider werden die Return-URL ohne https://
-Präfix nicht als gültige Return-URL akzeptieren.
Es ergeben sich zudem weitere Probleme, auf die hier nicht näher eingegangen wird.
ASP.NET Core hat glücklicherweise die Funktion eingebaut,
mit der die beiden Header X-Forwarded-For
und X-Forwarded-Proto
ausgewertet und berücksichtigt werden können.
Dies ist für viele Anwendungen wichtig:
Es erlaubt aber eben auch, dass die ASP.NET Core Applikation eine korrekte OIDC Return-URL generieren kann. Dazu muss folgende Konfiguration getätigt werden:
// Program.cs
using System.Text;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<ForwardedHeadersOptions>(
options =>
{
// Die Header müssen explizit ausgewählt werden
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// Es gibt zwei Reverse Proxies zwischen dem Browser
// und dem ASP.NET Core Container, nämlich
// 1. CDN, bspw. Cloudflare oder Azure Front Door
// 2. Azure Container Apps Ingress Service
options.ForwardLimit = 2;
// Header von Proxies aus allen Netzwerken werden berücksichtig.
// Dies kann so gemacht werden, weil Azure Container Apps kein direkten
// Zugriffe von aussen auf den ASP.NET Core Container möglich sind.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
}
);
var app = builder.Build();
// Die Reihenfolge der `app.Use*()` spielt eine wichtige Rolle!
// `UseForwardedHeaders()` sollte als Erstes konfiguriert werden,
// unbedingt auch vor `UseHttpsRedirection()`, `UseHsts()` oder `UseCors()`!
app.UseForwardedHeaders();
app.MapGet(
"/",
context => context.Response.WriteAsync(
$"Hello World to '{context.Connection.RemoteIpAddress}' via '{context.Request.Scheme}'",
Encoding.UTF8
)
);
app.Run();
return;
Mit dieser Konfiguration erstellt ASP.NET Core, bzw. die Microsoft Identity Platform, zuverlässig korrekt OIDC Return-Urls. Zudem kann jederzeit über den HTTP Context herausgefunden werden, wer den Request ursprünglich abgesetzt hat.
Weitere Informationen zur Konfiguration der X-Forwarded-*
-Header in ASP.NET liefert der Artikel Configure ASP.NET Core to work with proxy servers and load balancers.
Auch zum Ingress Service in Azure Container Apps gibt es weitere Informationen.
Jeden Freitag nehmen wir uns eine Stunde Zeit, um Fragen rund um die Entwicklung und Pflege von Software zu beantworten.
Zu solchen und weiteren deiner Fragen helfen wir dir gerne weiter. Die Stunde steht exklusiv dir zur Verfügung. Sie ist für dich komplett kostenlos und unverbindlich. Wir benötigen dafür lediglich deine Anmeldung: