Diagramm: Cognito ohne Custom Domain (Stack 1), Frontend mit CloudFront, Lambda@Edge und Apex-Weiterleitung (Stack 2), als dritter Schritt die Cognito Custom Domain. Pfeile zeigen die Abhängigkeitsrichtung, der vermeintliche Zyklus ist aufgelöst.

01Der richtige Entscheid

Auch der richtige Weg hat eine Schicht von Fallen davor.

In einem früheren Beitrag habe ich die Regel formuliert, an die ich mich seit Jahren halte: man baut kein eigenes Login-System. Cognito, Lambda@Edge und API Gateway lösen das Problem in drei sauberen Schichten. Den Stack betreibe ich seit November 2025 produktiv für SCMC.

Dieser Beitrag erzählt, was nach dieser Entscheidung passiert. Wenn du das selbstgebaute Login vermeidest, hast du eine Schicht von Fallen vor dir, die in keinem AWS-Referenz-Blog steht. Vier Fallen, die in fester Reihenfolge auftreten, weil das saubere Handling der einen die nächste sichtbar macht. Die letzte zeigt sich erst dann, wenn du den ganzen Stack als Infrastructure-as-Code beschreibst. Also genau dann, wenn du es richtig machst.

02Die Referenz ist von 2018

Der kanonische AWS-Blog funktioniert. Er stoppt dort, wo 2026er Sicherheit anfängt.

Die naheliegende Quelle für Cognito plus Lambda@Edge ist der offizielle AWS-Blog “Authorization@Edge: How to Use Lambda@Edge and JSON Web Tokens”. Ich habe ihn genau so umgesetzt: Cognito Hosted UI für den Login-Flow, Lambda@Edge für die Token-Validierung an CloudFront, JWT-Cookies für den Session-State.

Der Code aus dem Blog funktioniert. Er beschreibt eine vollständige, lauffähige Lösung. Nur ist der Beitrag aus Januar 2018, und ein Login auf dem Stand von 2018 hält dem Stand von 2026 nicht stand.

Was im Referenz-Beitrag nicht steht:

  • Passkeys, die seit 2024 in jeder ernsthaften Auth-Liste auftauchen.
  • Ein MFA-Flow jenseits der reinen User-Pool-Konfiguration.
  • Refresh-Token-Rotation.
  • Session-Invalidierung, also „auf allen Geräten abmelden”.

Cognito kann all das. Die Referenz erwähnt nichts davon. Wer den Beitrag literal umsetzt, hat einen funktionierenden, aber sicherheitstechnisch acht Jahre alten Login. Genau an dieser Stelle musste ich stehenbleiben und mir die Frage stellen, die der Blog nicht stellt: wo gehören Passkeys, MFA, Refresh-Rotation und Session-Invalidierung in diesen Stack?

03Der Service kann es, die Hosted UI nicht

Cognito unterstützt Passkeys und MFA. Die mitgelieferte UI deckt nur den Demo-Fall.

Mein nächster Schritt war, die fehlenden Schichten nachzurüsten. Cognito unterstützt MFA. Cognito unterstützt Passkeys. Das sind echte Features, kein Marketing-Häkchen.

Die Hosted UI macht für jedes davon genau das, was eine Verkaufsdemo braucht. Sie kann einen Passkey einrichten. Sie hat keinen Flow, um die Passkeys eines Nutzers aufzulisten, einen davon zu entfernen, oder eine frische Enrolment auszulösen. Bei MFA ist die Lücke noch grösser: die Hosted UI validiert TOTP nur beim Login. Einrichten und Verwalten muss die Anwendung selbst übernehmen.

Das Timing dieser Erkenntnis tat weh. Ich hatte mich für die Hosted UI entschieden, um genau diese Auth-Screens nicht selbst zu bauen. Wenige Wochen später schrieb ich die Screens, die in der Hosted UI fehlen, von Hand.

„Der Service unterstützt X” und „der Service hat einen brauchbaren End-to-End-Flow für X” sind zwei verschiedene Sätze. Die zweite Frage stellt vor dem Commitment niemand laut genug.

04Die eigene Domain hat eine Vorbedingung

Cognito Custom Domain verlangt, dass die Apex-Domain bereits existiert und auflöst.

Mein Ziel war eine eigene Auth-Domain. Login unter auth.example.com, Anwendung unter www.example.com, und die übliche Apex-Weiterleitung von example.com auf www.example.com.

Cognito Custom Domains haben eine harte Vorbedingung. Die Apex-Domain muss existieren und auflösen, bevor du die Custom Domain in Cognito anlegen kannst.

Mein erster Reflex war eine Abkürzung: ein A-Record auf example.com, der auf 127.0.0.1 zeigt. Die Apex-Domain löst auf, Cognito ist zufrieden, die Custom Domain lässt sich anlegen. Die echte Apex-Weiterleitung auf www wollte ich später nachziehen. Eine Weile lief das System genau so. Der Login funktionierte, alles ruhig.

Bis die ersten SEO-Checks aufschlugen. example.com löst auf 127.0.0.1 auf, meldeten die Tools, sieht aus wie eine kaputte Site, schadet der Indexierung. Also schnell die richtige Apex-Weiterleitung dazubauen. An genau dieser Stelle gibt CloudFormation eine Antwort, die ich nicht erwartet hatte: einen DNS-Record kannst du nicht in zwei Stacks gleichzeitig besitzen. Der Platzhalter-A-Record steckte im Auth-Stack, die ordentliche Weiterleitung gehörte logisch in den Frontend-Stack, und die scheinbar harmlose Abkürzung von vorhin war auf einmal ein Migrationsproblem zwischen zwei Stacks.

Für sich genommen ist die Apex-Vorbedingung eine Wartezeit. In Kombination mit der Architektur aus dem Referenz-Blog und mit der CloudFormation-Logik wird daraus eine Abhängigkeit, die sich beisst.

05Henne, Ei, und warum nur Infrastructure-as-Code es sieht

Die teuerste Stunde des Setups war keine Kryptografie und keine Sicherheitslücke.

Setze das Referenz-Setup (Cognito, CloudFront, Lambda@Edge, JWT-Cookies) und die Custom-Domain-Vorbedingung in einen einzigen CDK-Deploy zusammen. Was du bekommst, ist ein Abhängigkeits-Zyklus.

  • Die Webseite braucht den Login-Kontext, um zu funktionieren. Lambda@Edge validiert Cognito-JWTs an CloudFront, der Login-Endpunkt zeigt auf auth.example.com.
  • Der Login, genauer die Cognito Custom Domain für auth.example.com, verlangt, dass die Apex-Domain bereits existiert und auflöst.
  • Die Apex-Weiterleitung von example.com auf www.example.com entsteht aber als Teil des Webseiten-Stacks.

Im CDK-Deploy musst du also entscheiden, was zuerst entsteht. Die Webseite, an deren Apex die Cognito Custom Domain hängt? Oder das Login, das die Webseite zum Funktionieren braucht?

Diese Frage hat in keiner AWS-Doku eine Antwort. Sie taucht in der Konsole nicht auf, weil du dort von Hand klickst, in einer Reihenfolge, die dir selbst plausibel scheint, und die genaue Sequenz wird nirgends festgeschrieben. Erst wenn du den ganzen Stack als Infrastructure-as-Code beschreibst, so wie ein ernsthafter Serverless-Betrieb baut, wird der Zyklus überhaupt sichtbar. Der korrekte Bauweg ist es, der die härteste Falle erst hervorruft.

Die teuerste Stunde dieses ganzen Setups hatte mit Kryptografie nichts zu tun und auch nichts mit einer Sicherheitslücke. Sie ging dafür drauf, herauszufinden, welcher von zwei CDK-Stacks zuerst deployen muss.

06Die Auflösung: zwei Stacks, drei Schritte

Trennt man Cognito und seine Custom Domain, fällt der Zyklus auseinander.

Die Auflösung ist klein, sobald man sie sieht. Der Zyklus war nie echt. Er entstand allein dadurch, dass „Cognito” und „die Custom Domain für Cognito” als ein unteilbarer Block galten. Trennt man die Anlage von Cognito vom Anhängen der Custom Domain, ist der Knoten weg.

Konkret in drei Schritten über zwei CDK-Stacks:

Stack 1, Auth-Stack ohne Custom Domain. User Pool, App Clients, MFA-Konfiguration, Passkey-Konfiguration, Trigger-Lambdas, alles, was Cognito sonst zum Betrieb braucht. Was diesem Stack fehlt: die Custom Domain.

Stack 2, Frontend-Stack. S3-Bucket, CloudFront-Distribution, die Lambda@Edge-Funktionen, die Apex-Weiterleitung von example.com auf www.example.com. Nach dem Apply existiert die Apex-Domain und löst auf.

Schritt 3, am Ende von Stack 2: die Cognito Custom Domain. Erst jetzt, im selben CDK-Apply nach allen anderen Frontend-Ressourcen, wird die Cognito Custom Domain für auth.example.com angelegt. Die einzigen Eingaben, die sie braucht, sind der Domain-Name und der User Pool. Beides existiert zu diesem Zeitpunkt.

Die Reihenfolge im CDK-Apply: Cognito ohne Domain, dann Frontend inklusive Apex-Weiterleitung, dann die Custom Domain als Anhang an den schon bestehenden User Pool. Sobald der vermeintliche Zyklus an dieser Naht geteilt ist, gibt es ihn nicht mehr.

07Was ich daraus mitnehme

Drei Beobachtungen aus dieser Geschichte.

Erstens, die kanonische Referenz ist ein Startpunkt, kein Endpunkt. Der AWS-Blog von 2018 funktioniert, er trifft den 2026er Sicherheitsstandard aber nicht mehr von allein. Wer eine Auth-Schicht heute baut, muss die Lücken zwischen Referenz und Stand der Technik selbst schliessen, und sollte vor dem Build wissen, dass diese Lücken existieren.

Zweitens, „der Service unterstützt X” ist eine andere Aussage als „der Service hat einen brauchbaren End-to-End-Flow für X”. Diese Unterscheidung gilt für jede Managed-Service-Entscheidung. Vor dem Commitment auf eine Hosted UI lohnt sich der Blick in die Self-Service-Flows, die du am Ende selbst bauen wirst.

Drittens, die härtesten Infrastructure-as-Code-Fallen entstehen, wenn man zwei Dinge als unteilbar behandelt, die sich an der richtigen Naht teilen lassen. Cognito ohne Custom Domain plus Custom Domain als separater Schritt klingt offensichtlich, sobald man es sieht. Bis dahin sieht man einen Kreis.

Wer mit denselben Trade-offs am Tisch sitzt, kann mit mir ein Gespräch buchen. Die Auth-Schicht ist heute solide, ihr Aufbau war Lehrgeld, dieser Beitrag ist die Quittung dafür.