Das Symptom
Etwas, das jahrelang lief, läuft nicht mehr — und zwar nur in bestimmten
Situationen. Das Bezahlfenster im Rahmen der Bestellseite meldet, die Sitzung sei
abgelaufen. Der Kalender, der auf der Intranetseite eingebettet ist, zeigt eine
Anmeldemaske, obwohl man angemeldet ist. Ein Link aus der Rundmail landet auf der
Startseite statt im Kundenkonto. Öffnet man dieselbe Adresse direkt in einem
eigenen Tab, funktioniert alles einwandfrei. Im Privatmodus ist es reproduzierbar
kaputt.
Die „Layer 8“-Wahrheit
Ein Cookie ist nichts weiter als ein Zettel, den der Server dem Browser
mitgibt und den der Browser bei künftigen Anfragen wieder vorlegt. Das ist der
gesamte Mechanismus, mit dem das Web sich merkt, wer du bist — HTTP selbst hat
kein Gedächtnis. Und genau dieser Zettel wird seit einigen Jahren deutlich
zurückhaltender vorgelegt.
Der Grund dafür steht schon in der ursprünglichen Cookie-Spezifikation von 2011,
in einem Abschnitt mit der Überschrift Third-Party Cookies: Wenn eine Seite
Inhalte von fremden Servern nachlädt, können diese fremden Server Cookies setzen
und den Besucher damit über Seitengrenzen hinweg verfolgen — ohne dass er diese
Server je bewusst besucht hätte. Dagegen wurde das Attribut SameSite
geschaffen. Es begrenzt, wie der Nachfolgeentwurf es formuliert, den
Geltungsbereich eines Cookies auf Anfragen, die zur selben Site gehören.
Damit sind drei Zustände möglich, und alle drei erzeugen ein anderes Symptom.
Strict heißt: nur bei Anfragen innerhalb derselben Site. Wer von außen
hereinklickt, kommt abgemeldet an — der Entwurf beschreibt diesen Fall selbst als
Quelle erheblicher Verwirrung. Lax heißt: innerhalb derselben Site, und
zusätzlich bei Navigationen auf oberster Ebene. Das ist der Grund, warum der Link
aus der E-Mail funktioniert, das eingebettete Fenster aber nicht. None heißt:
immer — und nur dieser Wert trägt eingebettete Anmeldungen, Bezahlfenster und
Widgets.
Hier liegt die Falle, in die fast jeder tappt: SameSite=None ohne das Attribut
Secure ist kein abgeschwächtes Cookie, sondern gar keines. Der Browser wirft es
komplett weg. Wer die Umstellung also macht und nur die halbe Zeile setzt,
verschlimmert die Lage, statt sie zu verbessern — und sieht das oft erst in der
Produktion, weil lokal über http://localhost entwickelt wird.
Und dann bleibt eine unbequeme Wahrheit: SameSite ist eine Bitte des Servers,
keine Garantie. Schon RFC 6265 stellt es Browsern ausdrücklich frei, Cookies
im Fremdkontext überhaupt nicht zu senden. Was heute mit None; Secure noch
läuft, kann morgen an einer Browsereinstellung scheitern. Deshalb ist die beste
Antwort auf dieses Problem selten ein besseres Cookie-Attribut, sondern ein
Ablauf, der ohne Fremdkontext auskommt: umleiten statt einbetten. Dann ist alles
dieselbe Site, und es gibt nichts mehr zu blockieren.
Die echte, verifizierte Lösung
1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:
1.Zuerst feststellen, ob das Cookie überhaupt gesetzt wird
Zwei völlig verschiedene Fehler sehen gleich aus: Das Cookie wird nie gesetzt, oder es wird gesetzt und später nicht mitgeschickt. Die Antwort des Servers zeigt den Unterschied. Der Befehl gibt alle Kopfzeilen der Anmeldeantwort aus und filtert die Cookie-Zeilen heraus — sie enthalten auch die gesetzten Attribute.
curl -sS -D - -o /dev/null https://example.com/login | grep -i "^set-cookie:"
2.In den Entwicklerwerkzeugen nachsehen, was der Browser davon behalten hat
Mit F12 die Entwicklerwerkzeuge öffnen, im Reiter Netzwerk die betroffene Anfrage anklicken und die Cookies ansehen. Blockierte oder verworfene Cookies werden dort gesondert gekennzeichnet, meist mit einem Warnsymbol und einer Begründung. Das ist die verlässlichste Stelle: Sie zeigt nicht, was der Server wollte, sondern was der Browser tatsächlich getan hat.
3.Den Zusammenhang bestimmen, in dem die Anfrage läuft
Entscheidend ist nicht die Domain des Cookies, sondern das Verhältnis zwischen der Domain in der Adresszeile und der Domain, die das Cookie braucht. Sind beide dieselbe Site, ist die Anfrage `same-site` und alle drei Werte funktionieren. Sind sie verschieden — eingebettetes Fenster, Anmeldeverfahren über einen fremden Anbieter, Bezahlseite im Rahmen —, ist sie `cross-site`, und nur `SameSite=None` wird noch mitgeschickt. Ein Aufruf über einen Link von außen ist ein Sonderfall: eine `cross-site`-Navigation auf oberster Ebene, bei der `Lax` mitgeht und `Strict` nicht.
4.Das Cookie korrekt für den Fremdkontext auszeichnen
Braucht der Ablauf das Cookie tatsächlich im Fremdkontext, muss `SameSite=None` gesetzt sein — und zwingend zusammen mit `Secure`, sonst verwirft der Browser das Cookie vollständig. `HttpOnly` gehört bei Sitzungs-Cookies zusätzlich dazu, damit kein Skript sie auslesen kann. So sieht die vollständige Kopfzeile aus:
Set-Cookie: sid=ABC123; Path=/; Secure; HttpOnly; SameSite=None
5.Prüfen, dass die Auslieferung wirklich über HTTPS läuft
`Secure` bedeutet, dass der Browser das Cookie nur über eine verschlüsselte Verbindung annimmt und sendet. Läuft auch nur ein Schritt der Kette über `http://` — eine Weiterleitung, ein Zwischenserver, die lokale Entwicklungsumgebung —, fällt das Cookie lautlos weg. Der Befehl ruft die Seite bewusst unverschlüsselt auf: Es muss ein Statuscode 301 oder 308 mit einer `Location`-Zeile auf `https://` zurückkommen. Antwortet dort stattdessen die Anwendung selbst mit 200, wird ein Teil des Ablaufs unverschlüsselt bedient — und genau dort verschwindet das Cookie.
curl -sSI http://example.com/login | head -5
6.Wo möglich den Fremdkontext ganz vermeiden
Die haltbarste Lösung ist, das Problem nicht zu lösen, sondern loszuwerden. Statt eines eingebetteten Rahmens auf fremder Domain leitet man den Besucher auf die eigene Domain um und nach getaner Arbeit zurück — dann ist jede Anfrage `same-site` und keine Browsereinstellung kann dazwischenfunken. Für Anmeldungen über Dritte ist das ohnehin das übliche Verfahren.
7.Bei Links von außen das Zwei-Cookie-Modell erwägen
Wenn Nutzer regelmäßig aus E-Mails oder von Partnerseiten kommen und dann ausgeloggt landen, steht das Sitzungs-Cookie auf `Strict`. Der Nachfolgeentwurf zu RFC 6265 empfiehlt für diesen Fall zwei getrennte Cookies: eines mit `Lax` für den lesenden Zugriff, damit der Besucher überhaupt ankommt, und eines mit `Strict` für alles, was Daten verändert. Fehlt das zweite, wird vor der Aktion erneut angemeldet.