Einfache Azure-Architektur für B2B-Webanwendungen
Einführung in die einfache Architektur
In diesem Beitrag werden wir eine schnelle und recht einfache Architektur für das Deployment von Webanwendungen mit geringer bis mittlerer Komplexität auf Azure erkunden.
Die verwendeten Komponenten sind:
- Azure App Services
- Mit der integrierten App-Service-Authentifizierung EasyAuth
- Azure API Management (APIM)
- Azure Container Registry (ACR)
- Azure Key Vault
Zusätzlich integriert man typischerweise eine Datenbank nach Wahl, einen SSO-Anbieter für die Authentifizierung (in unserem Fall Azure Active Directory (AAD) in Verbindung mit Azure Active Directory B2C (AADB2C)) und optional Azure Storage für Datei- oder Blob-Speicherung.
Eine Diskussion darüber, warum diese Komponenten gegenüber anderen ähnlichen bevorzugt wurden, wird im letzten Teil dieses Beitrags geführt.
Walkthrough der einfachen Architektur
Ohne weitere Umschweife: Hier ist die vorgeschlagene Architektur, und ich führe euch Schritt für Schritt durch sie:

1.) Nicht authentifizierter Benutzer fordert die SPA an
Nun, hier ist der Punkt: Der Titel des Blogbeitrags lautet B2B-Webanwendungen. Bei den meisten öffentlich zugänglichen Websites würde ein typischer Benutzerfluss für die Authentifizierung so aussehen:
- Benutzer lädt die SPA
- SPA prüft, ob der Benutzer eingeloggt ist, und zeigt andernfalls den Login-Screen
- SPA verwaltet den Login des Benutzers (z. B. mit Authorization Code Flow mit PKCE)
Für B2B-Webanwendungen ist das oft ein unerwünschtes Szenario, da das Bundle selbst sensible Informationen enthalten kann (wie API-Aufrufe), die Informationen über die Intention der Anwendung, enthaltene Geschäftsentitäten, aber auch potenziell vollständige Geschäftsprozesse preisgeben. Unser gewählter Ansatz liefert einem Benutzer nichts, solange er sich nicht zuerst authentifiziert. Azure App Service bietet eine Middleware namens Easy Auth, die das mit wenig Konfiguration erreichen kann. Der Benutzer hat eine Session auf dem AppService, und solange die Authentifizierung nicht erfolgreich war, wird er zum SSO weitergeleitet. Es ist eine Azure- (und Azure App Service-)spezifische leichtgewichtige Implementierung dessen, was beispielsweise Oauth2-Proxy tut.
2.) SSO
Hierauf gehen wir nicht zu ausführlich ein. Welchen SSO/IdP ihr auch wählt, die Konfiguration wird leicht unterschiedlich sein. Ihr werdet immer ein Objekt benötigen, das die Anwendung mit einer clientId und einem Secret repräsentiert, mit dem ihr AppService konfiguriert. Easy Auth verwendet einen Hybrid Flow, der euch einen id_token mit Informationen über den Benutzer in der Weiterleitung zum AppService sendet (also ähnlich einem impliziten Flow), aber Access- und Refresh-Tokens über einen Backchannel gemäß einem Authorization Code Flow abruft. Stellt also sicher, dass implizite Flows für die Anwendung/den Client auf eurer SSO-Seite erlaubt sind, damit alles korrekt funktioniert.
3.) Authentifizierter Benutzer fordert die SPA an
Nach erfolgreicher Authentifizierung lässt Azure App Service die Anfrage durch. Wie ihr die SPA bereitstellt, liegt bei euch – eine naheliegende Wahl wäre ein einfacher nginx-Server, der das Bundle zurückgibt.
Zugangsdaten für Backend-Aufrufe der SPA abrufen
Easy Auth stellt zwei Endpunkte als GET auf dem App Service bereit, der das Frontend bedient:
- /.auth/me gibt ein JSON-Dokument zurück, das id_token, access_token und refresh_token (den ihr nicht benötigt) sowie weitere Metadaten von App Service enthält
- /.auth/refresh tauscht das vom AppService gespeicherte Refresh-Token beim SSO gegen einen neuen Satz aus Access- und Refresh-Tokens ein
Mit diesen zwei Endpunkten hat eure SPA alle notwendigen Werkzeuge, um gültige Tokens für den Einsatz bei Backend-/APIM-Aufrufen zu erhalten oder bei Bedarf neue Access- und Refresh-Tokens zu beziehen.
Wichtige Informationen zu Sessions und Refresh-Tokens
App-Service-Sessions und Refresh-Tokens sind zwei verschiedene, voneinander entkoppelte Dinge, und leider kümmert sich App Service nicht um die Lebensdauer eurer Refresh-Tokens. Das bedeutet, ihr könnt in einem inkonsistenten Zustand landen, wenn:
- Eure App-Service-Session noch gültig ist
- Euer Refresh-Token abgelaufen ist
Das Aufrufen des /.auth/refresh-Endpunkts führt ab diesem Punkt zu einem 403, und das Neuladen der Seite veranlasst das Bundle lediglich, neu zu laden und zu versuchen, die Tokens zu aktualisieren, was fehlschlägt. Der einzige Ausweg ist, auf den Ablauf der Session zu warten oder die Cookies zu löschen – beides natürlich keine guten Optionen. Die Standard-Session-Dauer für AppService beträgt zum Zeitpunkt des Schreibens 24 Stunden – meiner Meinung nach länger, als Refresh-Tokens gültig sein sollten.
Um nicht in dieser Schleife gefangen zu sein, müsst ihr die Session-Dauer von App Service so anpassen, dass sie kleiner oder gleich der Dauer eurer Refresh-Tokens ist. Das kann durch das Setzen einer Eigenschaft auf der Ressource subscriptions/{subscription-name}/resourceGroups/{resource-group-name}/providers/Microsoft.Web/sites/{your-appservice-name}/config/authsettingsV2 entweder über ein ARM-Template oder über den Azure Resource Explorer erreicht werden. Sucht nach dem Wert “cookieExpiration.timeToExpiration” und ändert ihn auf einen angemessenen Wert (z. B. 1 Stunde und 59 Minuten für die App-Service-Session, wenn eure Refresh-Tokens eine Ablaufzeit von 2 Stunden haben, nur um Randfälle bei Aktualisierungsfehlern zu vermeiden).
4.) SPA ruft APIM auf
Das ist wieder unkompliziert: Durch das Einbinden eines gültigen Access-Tokens kann die SPA auf APIs zugreifen, die auf APIM veröffentlicht wurden. Wir empfehlen, dass eure API JWT-Claims validiert, die den allgemeinen Zugriff auf die App erlauben, während das Backend hinter APIM Zugriff und feingranulare Berechtigungen validiert. APIM kann JWTs mithilfe der validate-jwt-Policy validieren. Das Vorhandensein der Policy (und die Vermeidung einer versehentlichen Konfiguration von APIM als für nicht authentifizierte Benutzer zugänglich) muss entweder manuell oder durch Automatisierung überprüft werden – aber bleibt dran für einen zukünftigen Blogbeitrag zu Best Practices für (gemeinsam genutzte, da recht teure) APIM-Instanzen.
Überlegungen zur Netzwerkarchitektur
API-Management-Dienste können mehrere Zwecke erfüllen, aber für uns waren die wichtigsten Treiber die Auffindbarkeit von APIs und ein zentraler Punkt für die Anwendung von Governance. Um dieses Ziel zu erreichen, möchte man typischerweise eine (oder sehr wenige) gemeinsam genutzte APIM-Instanzen haben (außerdem sind APIM-Instanzen sehr teuer), die typischerweise in gemeinsam genutzten Subscriptions/Netzwerken platziert und in der Verantwortung mehrerer Anwendungsentwicklungsteams (die oft ein gemeinsames Infrastrukturteam bilden) sind. Um Abhängigkeiten zwischen Anwendungsentwicklungsteams und APIM-Teams zu reduzieren, erfordert unsere Architektur keine netzwerkseitige Kopplung von App Services und APIM-Instanz (z. B. über Private Endpoints). Stattdessen sind jede App-Service-Instanz und die APIM-Instanz öffentlich verfügbar, haben aber auch eine statische IP für ausgehenden Traffic, die nach Bedarf für IP-Whitelisting verwendet wird. Mit diesem Gedanken gehen wir nun durch, was „hinter” APIM passiert.
A.) Aufrufe von APIM zu Backends
App Services, die Backends hosten, haben eine öffentliche IP-Adresse (ihr könnt auch die von Microsoft zugewiesenen Domainnamen verwenden – da es sich nur um Hintergrundverkehr handelt – um DNS- und SSL-Aufgaben zu vermeiden). Um Traffic durch APIM zu zwingen, platzieren wir eine Access Restriction auf dem App Service, die nur die APIM-IP erlaubt. Wir erzielen dasselbe Ergebnis (bei vielleicht etwas größerer Angriffsfläche in Richtung DoS, aber das ist nur eine Vermutung) wie bei vollständig privatem Routing – ohne den Netzwerkaufwand.
B.) Aufrufe von Backends zu On-Premises über APIM
Ihr könnt On-Premises-Systeme auch sicher über APIM exponieren, indem ihr den Zugriff auf APIs, die zu On-Premises routen, nur für App-Service-IPs whitelistet, auf denen Backends gehostet werden. Durch die Verwendung von App Service VNet-Integration in Verbindung mit einem NAT-Gateway mit fester IP könnt ihr den gesamten von euren App Services ausgehenden Traffic durch eine feste IP zwingen, die ihr whitelisten könnt. Um erfolgreich auf On-Premises-Systeme zuzugreifen, benötigt ihr daher:
- Eine spezifische IP, die einem oder mehreren eurer Backends zugewiesen ist, verifiziert durch die ip-filter-Policy
- Ein gültiges JWT mit Berechtigungen zum Zugriff auf die API, verifiziert durch die validate-jwt-Policy
Das ist wohl wieder dasselbe Ergebnis wie alles in einem SDN zusammenzuführen, mit weniger Aufwand, da ihr keine zwei Parteien benötigt (eine mit Kontrolle über die gemeinsame APIM-Subscription und eine mit Kontrolle über die Anwendungssubscription). Ein Einbruch ist nur möglich, indem eure App-Service-Instanz kompromittiert wird (auf die nur über bestimmte APIs auf APIM zugegriffen werden kann, wie in A.) erklärt).
C.) Aufrufe von App Services zu ACR und Key Vault
Um Container-Images sicher abzurufen und Secrets zu laden, benötigen eure App-Service-Instanzen Zugriff auf diese Dienste. Obwohl Netzwerkintegration existiert, verkompliziert sie die Dinge, da ihr typischerweise auch andere Dienste außerhalb eines Azure-Ökosystems habt (z. B. Agents, die eure Deployment-Pipelines ausführen).
Zum Abrufen eines Images durch App Service aus ACR verwenden wir eine (User Assigned) Managed Identity. Zum Pushen von Images verwenden wir ein in Azure AD bereitgestelltes Service-Konto mit entsprechendem Zugriff (und z. B. Zertifikatsauthentifizierung). Wir betrachten diesen Ansatz als sicher genug, um die ACR nicht hinter Netzwerkgrenzen zu verstecken.
Zum Lesen von Secrets aus Key Vault kann App Service dieselbe Managed Identity verwenden. Auch hier betrachten wir das als sicher genug, ohne Netzwerkintegration zu benötigen.
D.) Aufrufe von Backend zu Azure Storage und Datenbank
Für Azure Storage und die Datenbank (in unserem Fall PostgreSQL) haben wir eine Private-Link-Integration zu unserem Backend, da das Backend als einziger Dienst beide regelmäßig benötigt, und verweigern den gesamten Traffic auf den öffentlichen Endpunkten. Für manuelle Debugging-Zwecke fügen wir bei Bedarf Zugriffsausnahmen hinzu (z. B. eure IP auf dem Datenbankserver für eine bestimmte Zeit erlauben, wenn ihr eine Verbindung herstellen müsst, und die Ausnahme danach wieder entfernen).
Warum nicht Dienst X oder Architektur Z?
Azure bietet eine riesige Anzahl von Diensten – viel zu viele, um auf jedem davon ein Experte zu sein. Wir haben die meisten Optionen zum Betrieb einer Webanwendung auf unsere Art (also mit einem Java/SpringBoot + Angular-Stack) bewertet und uns für App Services für die meisten einfachen bis mäßig komplexen Szenarien entschieden und für Azure Kubernetes Service für alles, was darüber hinausgeht.
App Services bieten einen schnellen Weg, eure Anwendungen zum Laufen zu bringen (containerisiert oder nicht), haben DNS + SSL + Authentifizierung out-of-the-box, gute Monitoring-Tools (kostenpflichtig, aber einfach zu bedienen) und erfordern weniger umfangreiches Wissen für einen erfolgreichen und sicheren Betrieb (im Gegensatz zum oben genannten AKS). ist das ein klarer Vorteil gegenüber komplexeren Architekturen, die nicht nur Kenntnisse darüber erfordern, wie Anwendungen z. B. auf Kubernetes deployt werden, sondern auch Kenntnisse darüber, wie die Infrastruktur sicher betrieben wird B. auf Kubernetes deployed werden, sondern auch Kenntnisse darüber, wie die Infrastruktur sicher betrieben wird (ja, AKS hilft, ist aber keine magische Zero-Maintenance-Lösung – während eines typischen Cluster-Lebenszyklus kann viel schiefgehen). Am Ende des Tages werden die meisten IT-affinen Menschen ein grundlegendes Verständnis von IP-Whitelisting und der Einrichtung von Authentifizierung haben, was ausreichen sollte, um unsere vorgeschlagene Architektur sicher zu konzipieren und zu betreiben – das ist das „Einfach” in unserem Titel.
Wenn ihr diesen Blogbeitrag diskutieren oder herausfinden möchtet, wie wir euch bei der Entwicklung und dem Deployment eurer Software auf Microsoft Azure helfen können, nutzt den Kommentarbereich, schreibt mir gerne jederzeit eine E-Mail an vincenzo.sessa@cloudflight.io oder fügt mich auf LinkedIn hinzu. Danke fürs Lesen.
Agent Hub
Gehen Sie über vereinzelte AI-Piloten hinaus und schaffen Sie eine sichere, skalierbare Grundlage für Agentic AI in Ihrer gesamten Organisation.
Zur Demo




