Shownotes & Links
Schreibt uns Euer Feedback an [email protected]!
- INNOQ Podcast zu HTTP
- Skalierbare Web-Architekturen: Training iSAQB CPSA-Advanced WEB
- HTTP State Management Mechanism
- Using HTTP cookies
Transkript
Till: Hallo und herzlich willkommen zu einer neuen Folge des INNOQ Podcast. Heute mit Stefan Bodewig und Lucas Dohmen und mir, Till Schulte-Coerne, ich habe vor einiger Zeit schon mal den Podcast gemacht, das ist aber schon etwas länger her. Wir wollten uns jetzt alle drei nicht im Detail vorstellen, nur soviel: Wir geben alle drei die Web-Architektur Schulungen für die INNOQ im Rahmen des iSAQB Advanced Levels und wir wollten dem vielfachen Wunsch nach der HTTP Folge von Lucas und Stefan nachkommen und heute ein bisschen über Cookies quatschen. Das heißt, wir steigen direkt ein und ich frage mal den Lucas: Lucas, was sind denn jetzt nochmal Cookies?
Lucas: Erstmal Hallo zusammen, ich hatte hier in der HTTP Folge, mit dem Robert zusammen, erklärt, dass HTTP ein stateless protocol ist, dass die Kommunikation an sich statuslos ist und habe gesagt: Ja, an der Stelle müsste man jetzt noch einiges zu Cookie sagen und genau das wollen wir heute tun. Erst mal bedeutet das, es gibt Protokolle, da ist das so: Ich schicke ein Request hin und dann schicke ich einen zweiten Request hin und dann ist beiden Seiten bewusst, dass diese Requests zusammengehören. Wenn ich in dem ersten Request ein Login beispielsweise durchführe, dann weiß man, der zweite ist auch immer noch eingeloggt. Und das ist bei HTTP anders. Da steht jeder einzelne Request für sich. Alles, was man über den Request wissen muss, muss in jedem einzelnen Request wieder drinstehen. Und deswegen gibt es Cookies. Cookies ist ein Pärchen von http Headern, einmal ein Response Setter, den der Server setzt. Der heißt Set-Cookie. Da kann ich einfach einen Schlüssel auf einen Wert setzen. Beispielsweise sage ich da: User = Till. Und ab sofort speichert sich der Client das lokal ab. Das kann man auch in seinem Browser nachschauen. Das ist in jedem Browser irgendwo anders, aber meistens irgendwo unter einem Storage Tab, in den Developer Tools, da kann man die Cookies sehen und der Browser wird jetzt für jeden weiteren Request, den er ausführt diesen Cookie mitschicken und damit kann der Server das dann wieder auslesen. Und das überlebt damit auch Seitenwechsel. Das heißt also: Ich logge mich ein. Danach wird bei jedem Mal das, was der Server mir als Cookie geschickt hat, wieder mitgeschickt. Wir kommen noch mal ganz im Detail dazu, was das bedeutet. Aber das ist erstmal das Grundprinzip. Das heißt, das wird in jedem Fenster des Browsers mitgeschickt. Das ist also nicht für einen einzelnen Tab, sondern es wird wirklich für alle Fenster des Browsers mitgeschickt.
Till: Und zwar bei jeder Interaktion des Browsers mit dem entsprechenden Server, der den Cookie gesetzt hat.
Lucas: Genau.
Till: Welche Limitierungen gibt es denn da? Kann ich jetzt potenziell anfangen und wenn ich clientseitig 3 MB Bilddateien habe, die in ein Cookie reinschreiben?
Stefan: Theoretisch könntest du, nur nicht in ein Cookie. Du könntest einfach ganz furchtbar viele Cookies machen, wenn du möchtest. Fangen wir mal an damit, Lucas hat schon gesagt, das ist ein Schlüssel-Wert-Paar, das ich im Prinzip habe. Ich habe den Schlüssel gleich und dann irgendeinen Wert, der in diesem Cookie Header übertragen wird. Da kann ich nicht beliebige Zeichen reinschreiben. Weder für den Schlüssel, noch für den Wert. Da muss ich mich beschränken auf einen bestimmten Zeichenvorrat, aber ich könnte natürlich die 3 MB Bilddatei, die du hochladen möchtest, codieren, sodass sie nur aus Buchstaben und Zahlen besteht und dann wird das Ganze schon funktionieren. Tatsächlich gibt es eine Längenbeschränkung für das einzelne Cookie. Ein einzelnes Cookie darf 4000 Zeichen lang sein. 4000 Zeichen sind eine Menge auf der einen Seite. Auf der anderen Seite, wenn man 3 MB hochladen möchte, dann muss ich 12000 Cookies haben. Und ich habe jetzt falsch gerechnet, es ist eine grobe Schätzung. 750 hätten vielleicht gereicht, aber egal. Tatsächlich werde ich wahrscheinlich auch nicht beliebig viele Cookies setzen können, sondern da wird dann irgendwann der Browser auch sagen: Du hast wohl nicht mehr alle und das lassen wir mal schön sein. Das eine ist, dass ich tatsächlich eine Beschränkungen habe, die sich aus der Spezifikation ergibt. Ich habe auf der anderen Seite natürlich auch ganz praktische Beschränkungen, wie du gerade schon gesagt hast, und auch Lucas gesagt hat, das schickt der Browser fortan mit jedem einzelnen Request mit. Das heißt, mit jedem einzelnen Request übertrage ich das Cookie. Und jeder einzelne heißt nicht nur, wenn ich die nächste HTML-Seite lade, sondern auch, wenn ich das nächste Bild lade, wenn ich die nächste Stylesheet Datei lade, was auch immer sonst noch möglicherweise ein Request Style ist. Da kommt eine Menge Traffic zusammen. Auch wenn es nicht die 3 MB sind, mag es sein, dass wir mit dem Cookie immer eine ganze Menge durch die Gegend bewegen. Das wird mit HTTP/2 natürlich ein bisschen besser. Lucas hat es in der Folge mit Robert schon erwähnt, dass ich im HTTP/2 sagen kann: Übrigens möchte ich das gleiche Cookie senden, das ich beim letzten Request gehabt habe, nur noch einen Rückverweis habe. Aber trotzdem, logisch kommt dann eine ganze Menge an Daten zusammen und solange wir bei HTTP/1 sind, haben wir damit ein Problem. Dazu kommt, dass wir auf unserer Strecke vielleicht den einen oder anderen Server dazwischen haben, irgendwelche Intermediaries, die damit nicht unbedingt zurechtkommen, wenn sie mit besonders großen HTTP Headern umgehen sollen, die dafür einfach nur einen bestimmten beschränkten Zeilen Speicherbereich vorgesehen haben. Das würde ich so jetzt erstmal als die ersten Beschränkungen sehen. Grundsätzlich, das Cookie liegt im Client. Das heißt, der Client kann das natürlich auch manipulieren und kann Dinge daran tun und ändern, aber dafür ist es auch erstmal gedacht.
Till: Das ist keine Limitierung, sondern das ist eigentlich ein Feature und es gehört dem Client und er schickt es ab jetzt mit. Aber wir können nicht darauf vertrauen, dass da immer noch das drinsteht, was wir ursprünglich mal reingeschrieben haben. Wo kommen denn Cookies überhaupt her? Wer hat sie denn eigentlich mal erfunden? Und warum gibt es das heutzutage in den Browsern?
Stefan: Tatsächlich war ich damals auch noch nicht ganz dabei, sondern Cookies gibt es schon ein bisschen länger, als ich Webentwicklung intensiver betreibe. Aber ursprünglich ist es tatsächlich nur für einen einzigen Browser entwickelt worden. Wir reden allerdings von Anfang der 90er Jahre und wenn der einzige Browser Netscape war, dann gab es da auch nicht so wahnsinnig viele anderen zu dem Zeitpunkt. Netscape war anders als der Firefox heute nicht ein Open-Source-Projekt, sondern das ist eine Firma, die dahinter gesteckt hat, die den Browser produziert haben. Diese Firma hat zusammengearbeitet mit jemandem, der einen echten Webshop ins Web bringen wollte, Anfang früher 90er. Und in dem Kontext sind dann wohl Cookies erfunden worden, damit man sich nicht für jeden begonnenen Kaufprozess den Warenkorb auf dem Server merken muss, wenn die Leute damals auch nicht nochmal vorbeikommen, sondern um diesen Zustand für den einzelnen Bestellprozess im Client zu behalten. Und erst wenn man wirklich bestellt wird, hat sich der Server dann alles ernst genommen und gespeichert. Und die bis dahin angefangenen Transaktionen sollten nicht gespeichert werden. Andere Dinge hat man einfach später dann hinzugefügt. Ideen hat es genug gegeben, nachdem das Konzept des Cookie da war, was man damit machen kann. Sich also zu merken: Wo bin ich denn ursprünglich als Benutzer gewesen, um mir eine Historie anzubieten? Oder dass ich mir irgendwelche Präferenzen merken konnte, aber eigentlich die ursprüngliche Idee war eine reine Netscape Idee, die dann später auch übernommen wurde. Es gab für relativ kurze Zeit sogar einen Set-Cookie2 Header, weil man versucht hat das zu standardisieren, aber die Netscape Implementierung nicht haben wollte, aber am Ende hat sich die große Gemeinde der Leute, die da irgendwas standardisiert haben doch darauf geeinigt: Wir machen das so wie Netscape wollte und ein Set-Cookie definiert haben. Und das ist das, was wir heute haben. Eine ganze Zeit lang konnte man das in den Browsern noch gar nicht ausschalten. Das ist erst deutlich später gekommen, dass Benutzerinnen Kontrolle darüber haben, was mit ihren Cookies passiert. Jetzt habe ich schon ein paar Ideen gehabt, die später Leute gehabt haben, was man im State reinschreiben kann. Was gibt es denn sonst noch so an möglichen clientseitigen States?
Lucas: Dazu muss man erst mal überlegen: Was bedeutet eigentlich clientseitig in dem Kontext und da finde ich ganz wichtig, sich einfach nochmal daran zu erinnern, dass der Client in dem Fall das Gerät oder genauer genommen sogar der Browser vom Benutzer oder Benutzerin ist. Das heißt, es sind Informationen, die nicht an einer Person hängen, sondern an einem Gerät. Das heißt, ich könnte als Lucas einmal etwas abspeichern, was an meinem Computer hängt und einmal was, was an meinem Smartphone hängt. Nur ein Beispiel dafür wäre eine UI-Einstellung. Ich könnte sowas haben wie: Ich möchte den Dark Mode benutzen. Und zwar möchte ich den nur auf meinem Telefon benutzen, aber nicht auf meinem Computer. Auf meinem Computer möchte ich den Light Mode benutzen. Das wäre jetzt ein Beispiel, wo man erkennt, man kann durchaus darüber nachdenken: Soll die Information jetzt an an mir als Person hängen oder soll das an an meinem Gerät hängen? Stefan hat schon erwähnt, so etwas wie ein Warenkorb. Da können wir auch darüber nachdenken, wenn ich mit zwei Geräten eingeloggt bin, ob meine Erwartung ist, dass wenn ich etwas in den Warenkorb werfe, es auf beiden im Warenkorb liegt oder ob das nur auf einem Gerät im Warenkorb liegt. Wenn ich auf meinem iPhone was in den Warenkorb werfe, das auch auf meinem Mac im Warenkorb ist? Das ist eine fachliche Entscheidung, die wir treffen müssen. Aber erstmal ist es ein clientseitiger State, den wir zum Beispiel für so was wie eine UI-Einstellungen oder so etwas wie den Warenkorb benutzen könnten.
Till: Wenn man das ein bisschen weiterdenkt, dann gehört da eigentlich auch wirklich jede Variable im Browser dazu. Clientseitiger State beinhaltet streng genommen wirklich jede Variable, die ich in JavaScript definiere bzw. deren Wert. Wenn ich mir zum Beispiel den Dark Mode merken möchte, dann kann ich das, solange ich zumindest die Seite nicht verlasse und damit die Variable invalidiere, kann ich das natürlich auch einfach in JavaScript machen. Habe ich jetzt den Dark Mode an oder nicht? Oder was auch noch im clientseitigen State eigentlich existiert. Das ist jetzt ein bisschen weit hergeholt, aber gehört irgendwie auch noch dazu, dass ich vielleicht auch sage: Ich cache Daten einfach für den nächsten Request. Das mache ich dann häufig auch wieder in Form von JavaScript, aber dafür kann ich auch sowas wie den Browser Cache eigentlich als clientseitigen State Händler ansehen, dass ich mir Sachen schon mal runterlade, weil ich weiß, dass ich sie gleich benutzen muss oder dass ich mir Sachen merke, weil ich weiß, dass ich sie gleich wieder brauche, zum Beispiel. Aber das sind beides eigentlich keine klassischen Dinge, die irgendwas mit Cookies zu tun haben. Es gibt eine Vielzahl von clientseitigen State, aber mit Cookies behandelt man eine ganz besondere Form von clientseitigen State. Und die wichtigste ist eigentlich eine andere.
Lucas: Die allerwichtigste und ich würde fast sagen unverzichtbare ist die Identifikation von Benutzern. Wenn wir darüber nachdenken, jemand loggt sich ein und wollen dass in einem nächsten Request die Person noch eingeloggt ist, dann müssen wir wissen, dass es diese Person ist. Das heißt, wir müssen irgendeine Referenz zu einem Benutzer speichern, zu diesem spezifischen Browser. Da gibt es verschiedene Möglichkeiten, da können wir auch gleich noch mal darauf eingehen. Aber das ist jetzt erst mal die eine Möglichkeit. Also ich logge mich bewusst in eine Anwendung ein und danach gibt es irgendwie eine Referenz zwischen meinem Client, das ist in meinem Cookie drin und das wird zugeordnet zu irgendeiner Art von Benutzer. Ein Sonderfall davon ist eine anonyme Sitzung, dass ich eingeloggt bin, ohne mich bewusst eingeloggt zu haben. Man könnte das zum Beispiel so machen: Ich gehe auf die Webseite oder das erste Mal, dass ich darauf gehe, wird mir einfach direkt ein Cookie geschickt. Da steht irgendwie: Ich würfel eine Zufallszahl aus und ab sofort wird immer jedes Mal, wenn ich auf die Seite wieder gehe, dieser Cookie mitgeschickt und das könnte ich benutzen, um so eine User Journey aufzuzeichnen. Dann würde ich sehen: Okay, der Till ist auf diese Seite gegangen, dann hat er auf den Warenkorb geklickt, dann ist er doch wieder zurückgegangen, dann hat er dieses Ding angeschaut, hat es aber nicht in den Warenkorb gelegt. Dann ist er auf die nächste Seite gegangen, das hat er in den Warenkorb gelegt. Und das kann ich dann in einen großen Hadoop-Cluster werfen und ganz schlaue Sachen damit machen und dann herausfinden, welche Waren die beliebtesten sind.
Till: Genau. Aber ohne die Information, dass es der Till ist. Der Punkt dabei ist wirklich, dass es anonym ist und dass es völlig unerheblich ist, wer es ist, weil ich es am Ende in mein Hadoop-Cluster werfe und die Daten nicht in einem Kontext eines bestimmten Users auswerten will, sondern einfach alle meine User Journeys auswerte. Vielleicht können wir uns darauf verständigen, dass wenn man das nicht positiv mit der Erhebung von User Journeys umschreiben wollen, wir das einfach Tracking nennen, also Benutzer Tracking eigentlich. Was man machen kann, ohne dass Leute sich einloggen. Wenn die Leute sich eingeloggt haben, kann man sie auch tracken. Dann kann man aber sogar noch dran schreiben, wer es war.
Lucas: Weil wenn man üblicherweise den Browser nicht ganz anders eingestellt hat, der Browser erst mal ein Cookie, der von der Seite kommt, auf die man geht, annimmt und auch dann mitschickt. Das ist jetzt erst mal etwas, was der normale Browser erstmal tut. Das heißt, man kann das sehr gut benutzen, um dieses ganze User Tracking durchzuführen, was heutzutage im Web gang und gäbe ist, würde ich jetzt mal einfach behaupten.
Stefan: Ist ein Cookie die einzige Möglichkeit, in der ich clientseitigen State halten kann oder gibt es da noch irgendwelche Alternativen?
Till: Ich hatte eben schon gesagt, dass letzten Endes wirklich jede JavaScript Variable ein clientseitiger State ist, aber natürlich mit der Restriktion, dass wenn ich die Seite zumache und der JavaScript Thread beendet wird, die Variable futsch ist. Es gibt aber eine ganze Reihe von APIs, HTML5 APIs, wie das so schön heißt, die letzten Endes mir, ähnlich wie es ein Cookie tut, erlauben, dass ich mir in meiner Browser Instanz bestimmte Dinge merken kann. Also das bekannteste da sicherlich, das taucht auf in demselben Inspector Tab im Browser typischerweise auf, den Lucas gerade erwähnt hat, ist local storage. Wenn ich mir den Inspector für irgendeine Website anschaue und reinschaue: Was hat sich diese Seite alles gemerkt? Dann sehe ich da typischerweise meine Cookies und darunter sehe ich direkt: Was steht denn alles Local Storage drin? Und wenn zum Beispiel Google versucht mich auf irgendeiner Website zu tracken, dann setzen die typischerweise ein Cookie und schreiben die Informationen auch nochmal in Local Storage rein, damit sie sich möglichst sicher sind, dass sie nichts verlieren. Es gibt da aber auch noch eine ganze Reihe weiterer Persistenzmechanismen in Form von HTML5 APIs. Zum Beispiel gibt es die IndexedDB als SQL Datenbank im Browser. Die würde auch überhaupt keinen Sinn machen, wenn sie jedes Mal leer wäre, wenn ich eine Seite wechsle, sondern sie dient primär dazu, dass ich mir Daten wirklich langfristig merken kann. Oder was auch relativ neu ist, dass ich zum Beispiel auch in der Browser Instanz einen gemeinsamen Speicherbereich machen kann, wo dann sogar noch JavaScript Logik läuft. Das wären dann eben sowas wie Worker oder speziell sogar SharedWorker, die dann irgendwie im Hintergrund laufen und Informationen halten, auf die dann andere Browserfenster zugreifen können. Aber im Kern kann man schon sagen: Solange ich eine Seite einfach nicht verlasse, ist eine JavaScript Variable clientseitiger State. Und dementsprechend, wenn man sich moderne Single Page Architekturen anschaut, ist das auch so. Ich logge den User ein und wenn ich dann irgendein Token zurückbekomme, mit dem ich mit einer API interagieren kann, dann merke ich mir dieses Token vielleicht einfach in der Variable. Das heißt natürlich, dass es weg ist, wenn ich die Single Page abzumachen, das kann man auch als Feature ansehen und nicht unbedingt als [17:27], wenn ich es dann persistieren will, muss ich was anderes machen.
Lucas: Aber auch dieses ganze Thema von Variablen, die man im Speicher hält, ist auch etwas, was man in solchen Patterns wie jetzt der Redux Architektur oder der Redux Library, die ist eigentlich nur dafür da, solchen State zu verwalten. Das ist keine theoretische Möglichkeit, es ist etwas, was ganz praktisch in React oder Angular Anwendungen getan wird. Aber auch außerhalb von JavaScript gibt es auch noch andere Möglichkeiten State zu speichern. Eine Möglichkeit wäre der Browser Cache. Also grundsätzlich hat Till schon erwähnt, man könnte die letzten 15 Artikel, die ich mir angeschaut habe, in der JavaScript Variable speichern. Ich könnte aber auch die letzten 15 Seiten, auf denen ich war in einem Browser Cache speichern und wenn ich dann zurück gehe, dann werden die aus dem Browser Cache gelesen. Das wäre auch eine Möglichkeit. Aber grundsätzlich könnte ich den Browser Cache und das wird auch teilweise getan, auch zum Tracking benutzen. Also ähnlich wie wir das hier machen, könnte man auch, indem man den E-tec Header etwas missbräuchlich benutzt, dafür verwenden, jemanden zu verfolgen, ohne dass diese Person überhaupt Cookies aktiviert hat. Der Browser hat jetzt auf jeden Fall auch eine Möglichkeit, User-spezifische Daten abzulegen. Und ganz wichtig ist: Die URIs sind auch ein Ort, um sowas abzulegen. Wenn ich mir jetzt überlege: Ich möchte sowas wie ein Wizard machen, wo ich mich schrittweise durch irgendeinen Prozess durch bewege, dann könnte ich in jedem Schritt einfach das, was ich zusätzlich an Informationen gegeben habe, als Query Parameter an meine URL dranhängen und so schrittweise mein State weiter aufbauen, bis ich es dann schlussendlich abschicken. Das wäre jetzt ein Beispiel, wie wir Sachen in der URI speichern. Ganz allgemein gesprochen werden einfach auch clientseitiger State in URIs abgelegt. Interessanterweise ist es auch eine Möglichkeit zwischen Fenster State oder Tab State, also für einen Browser Tab und etwas, was für die gesamten Browser gilt, zu unterscheiden. Wenn ich irgendwie möchte, dass eine Information nur für diesen einen Tab gespeichert wird und nicht für alle Tabs, dann wäre auch die URI eine Möglichkeit das abzulegen.
Stefan: Du hast eben gesagt, als du über die Art und Weise, wie du die angemeldeten Benutzer referenzieren würdest, gesprochen hast, da gäbe es verschiedene Möglichkeiten, wie man das mit Cookies realisieren könnte. Name = Wert. Da kann man einfach sagen: Login = Lucas. Was hast du sonst noch für Möglichkeiten?
Till: Ja, im Prinzip diese Herangehensweise, wie Lucas ganz am Anfang gesagt hat, kann man sich natürlich fragen: Ist das so eine gute Idee? Ich sage jetzt einfach: Set Cookie Login = Lucas und speichere das einfach im Klartext auf dem Client. Das wäre jetzt vielleicht für einen Login nicht die beste Idee, weil wir auch schon gesagt haben, dass man diesen Cookie im Client manipulieren kann. Dann könnte er Lucas hingehen, sich ein Cookie ausstellen lassen, wo Login = Lucas drinsteht. Und da Login = Till reinschreiben und dann ist er plötzlich in meinem Namen unterwegs. Das wäre also nicht so optimal. Was ich aber an dieser Stelle machen kann ist, dass ich die Daten, die ich in den Cookie reinschreibe, einfach signiere, indem ich einfach eine Signatur für dieses SchlüsselWert-Paar berechne und sie einfach mit in den Cookie reinschreiben. Typischerweise benutzt man dann sowas wie HMAC und einen serverseitiges Secret, irgendeine Zufallszahl auf dem Server, die auch nur der Server kennt und nicht irgendwer sonst. Und ich gehe dann einfach hin und sage: Wenn ich dieses Cookie schreibe, das Set-Cookie Header steht im Response, wird also vom Server geschrieben. Wenn ich diesen Cookie schreibe, signiere ich einfach das Cookie direkt und schreibe das in den Wert des Cookies mit rein. Und damit bin ich dann in der Lage, wenn der Client mir einen neuen Cookie schickt oder einen Cookie zuschickt, von dem er behauptet, der käme von meinem Server, kann ich überprüfen, ob ich ihn tatsächlich ausgestellt habe. Weil wenn Lukas jetzt hingegangen wäre und einfach Login = Till reingeschrieben hätte, würde die Signatur nicht mehr zum Cookie passen und der Server könnte das zum Beispiel ablehnen. Das muss man für viele Dinge gar nicht machen. Zum Beispiel für sowas wie den Dark Mode wäre das vielleicht totaler Unsinn. Wenn ich jetzt sage, ich möchte mir einfach nur merken, ob Lucas in seinem Browser den Dark Mode anhaben möchte oder nicht, dann kann Lucas diese Information auch sehr gerne mit seinen Seiten Inspektor manipulieren, ohne dass mich das irgendwie interessiert. Für andere Dinge, für andere Extreme könnte es aber vielleicht tatsächlich sogar notwendig sein, die Dinge zu verschlüsseln. Das hängt einfach von der Information ab. Anstelle sie nur zu signieren, könnte ich natürlich auch noch den Schritt weitergehen und sagen: Lucas soll in seinem Inspektor nicht mal sehen, dass er als Lucas eingeloggt ist, sondern Lucas soll nur eine Bild-Zeichenkette sehen. Das würde bedeuten, dass ich meinen serverseitiges Secret einfach dazu nehme, um einfach verschlüsselte Daten von vornherein in den Cookie reinzuschreiben. Und das macht eigentlich nicht so wahnsinnig viel Sinn, weil man eigentlich den Speicherbereich, in dem die Cookies im Browser liegen, vertrauen muss. Es macht keinen Sinn, so zu tun, als wäre der Speicher in Lucas Browser etwas, wo ich keine bestimmte Information nicht reinschreiben darf. Lucas Benutzername wäre zum Beispiel auch nichts wirklich wahnsinnig Vertrauliches. Aber wenn die Informationen vertraulich sind und zum Beispiel nicht in irgendwelchen intermediaries, in irgendwelchen Log Files im Klartext auftauchen sollen, wäre das theoretisch natürlich auch irgendetwas, was man machen könnte. Einfach sämtliche Daten in den Cookie zu schreiben und da zu unterschreiben oder zu signieren oder zu verschlüsseln, ist definitiv eine Variante, um alle möglichen Informationen im Cookie zu schreiben. Das hat aber dann natürlich die Limitierungen, die Stefan am Anfang erwähnt hatte mit der Größe. Die 3 MB große Bilddatei gehört natürlich nicht in irgendwelche Cookies rein, sondern die Informationen, die man direkt in den Cookie schreibt, müssen auch wirklich sehr gering sein.
Lucas: Anders ist es, wenn man sich überlegt, dass man diese Sachen auch auf der Serverseite speichern möchte. Dafür wäre es dann einfach möglich in den Client jetzt nicht hinzuschreiben: Username = Till, sondern einfach eine Zufallszahl auszuwürfeln und die in den Cookie zu speichern. Das heißt, jedes Mal, wenn der gleiche Browser wieder auf meinen Server zugreift, wird diese gleiche Zufallszahl mitgeschickt und die kann ich dann als Identifier benutzen, um etwas zu suchen. Dafür gibt es dann im Groben erstmal zwei Möglichkeiten. Die erste ist: Ich habe einfach in meinem Speicher eine Hashmap oder ein Dictionary, je nachdem welche Sprache ihr bevorzugt. Da ist das einfach der Schlüssel. Und der Value ist einfach ein beliebiger Block von Daten. Da kann ich einfach alles reinschreiben, was auch immer ich möchte. Das haben einige Menschen in ein Extrem geführt und haben das missbraucht, um Caching quasi auf dieser Ebene durchzuführen. Das heißt also, anstatt dann auf die Datenbank zuzugreifen, schaut man nur in dieser Hashmap nach und fertig. Das ist dann doof, wenn der Request an einen anderen Server geht. Das ist sehr doof. Genauso ist es doof, wenn der Server mal abstürzt und diese ganzen Daten weg sind. Und allgemein würde ich auch behaupten, ist es sowieso keine Top Idee solche Informationen verlierbar im Speicher zu halten. Aber eine Variante davon ist, das tatsächlich nicht einfach im Speicher zu halten in der Hashmap, sondern dafür einfach die Datenbank zu benutzen. Man könnte also in der Datenbank eine Tabelle anlegen, die Sessions heißt und da ist die ID diese Zufallszahl, die ich ausgewürfelt habe. Und dann habe ich eine Spalte, da steht zum Beispiel der User Name drin. Und wenn ich dann einen Zugriff habe vom Client, dann schaue ich in die Sessions Datenbank. Da sehe ich, dass es diesen Sessions-Eintrag in der Datenbank ist, da dieser Username drinsteht und dann kann ich für den Usernamen den entsprechenden Menschen in der User Datenbank heraussuchen. Das wäre zum Beispiel eine Möglichkeit. Ich kann aber auch in dieser Tabelle Daten ablegen, die geräte-spezifisch und nicht user-spezifisch sind, wie ich eben schon erzählt habe. Noch eine Variante davon wäre nicht meine Hauptdatenbank zu verwenden, sondern eine Sekundär-Datenbank. Am beliebtesten in dem Bereich würde ich sagen, sind dabei Reduce und [26:37]. Das sind die zwei gängigen Key Value Stores. Auch wenn das etwas vereinfacht ist für Reduce es als Key Value Store zu bezeichnen, aber sei es drum. Da kann ich auch sagen: Ich lege jetzt in meinem Key Value Store einfach ein beliebigen Datenblock ab und das hat den Vorteil im Vergleich zum Hauptspeicher, dass darauf auch andere Instanzen von meinem Rechner darauf zugreifen können. Reduce und [27:01] die haben auch die Möglichkeit, solchen Sachen dann auch ein Ablaufdatum zu geben, dass sie dann irgendwann einfach rausfliegen. Das wäre etwas, was ich in der Datenbank Case selbst machen muss oder über den Cookie machen muss. Das wären so die Varianten, wo man nur eine Zufallszahl auf dem Client speichert.
Till: Jetzt haben wir sehr viel High Level geredet. Eigentlich haben wir gar nicht mehr über Cookies geredet, sondern wir haben über Sessions geredet. Ich hoffe, das war okay. Clientseitig, serverseitige Sessions, was auch immer. Aber eigentlich wollten wir über Cookies reden. Das heißt, wir sollten jetzt vielleicht noch mal zusehen, dass wir auf den Kern der Cookie API wieder zurückkommen, also dem Set-Cookie und den Cookie Header in Response bzw. Request auf HTTP Ebene. Und da haben wir euch ganz am Anfang ein bisschen die Unwahrheit erzählt. Dieser Cookie Header, der ist nicht so einfach, dass man einfach nur Set-Cookie: Key = Value sagt, sondern der Header beinhaltet auch noch eine ganze Reihe weiterer Parameter, die dann so ein Semikolon getrennt dahinter kommen. Und die setzen eine ganze Reihe an weiteren Features um, die wir jetzt mal kurz auf jeden Fall anschauen wollen. Das erste ist die Frage danach: Wie lange ist denn eigentlich so ein Cookie gültig? Das würde ich jetzt mal Stefan einfach fragen: Wie kann ich denn eigentlich steuern, wann dieser Cookie wieder gelöscht wird, wie lange er mitgeschickt wird? Gibt es da für einen Default? Kann ich das überschreiben?
Stefan: So wie wir es zu Anfang beschrieben haben, haben wir gesagt: In dem Moment, wo das Cookie einmal gesetzt ist, sendet der Browser das bei jedem Folge-Request immer wieder mit. Und das ist, wenn wir nichts anderes sagen, zumindest so lange richtig, wie das Browserfenster offen bleibt. Das heißt, da würde bis in alle Ewigkeit, wenn das Browserfenster bis in alle Ewigkeit offen bleibt, der Browser immer wieder das gleiche Cookie mit dem gleichen Wert senden.
Till: Du meinst jetzt hier an der Stelle nicht das Fenster oder den Tab, sondern wirklich den Browser.
Stefan: Die Anwendung im Browser, das ist richtig. Gut, dass du da noch mal kritisierst. Für mich ist das Fenster die Anwendung. Es gibt zwei verschiedene Gründe, warum ich das vielleicht nicht möchte auf der Serverseite. Das eine kann natürlich sein, dass ich gar nicht will, dass das Cookie so lange offen bleibt. Wenn jemand seine Browser Anwendung tagelang nicht schließt, wochenlang nicht schließt, weil er ein Betriebssystem hat, das nicht mal [29:30] muss, dann könnte es natürlich sein, dass das vielleicht gar nicht das ist, was ich möchte, dass ich eine maximale Lebensdauer haben möchte. Bei einem Session Fall würde ich vielleicht sagen, nach einer halben Stunde ist auch gut. Und wenn die rum ist und tagelang keine Interaktion da war, dann sollte danach vielleicht was Neues beginnen, wenn der Browser das nächste Mal vorbeikommt.
Till: Das heißt zum Beispiel, dass du bei einer Session auch jedes Mal, wenn eine Interaktion da wäre, den Cookie neu schreiben würdest?
Stefan: Genau, wenn ich sagen wollte. Vielleicht muss ich jetzt auch tatsächlich auf die konkreten Attribute eingehen. Wenn ich sagen würde: Bitte, das soll jetzt für eine halbe Stunde gültig sein. Ich habe also die Möglichkeit, relative Angaben zu machen, also Max Age Attribut, mit dem ich sage: Das nehme ich jetzt für 30 Minuten oder in Sekunden ist es dann eben gemessen für die nächsten 1800 Sekunden und so lange soll dieses Cookie leben. Wenn die rum sind, sind sie rum. Und wenn ich zwischendrin nicht ein neues Cookie gesetzt habe, wäre es dann plötzlich weg. Wenn ich aber möchte, dass eine Session 30 Minuten nach der letzten Benutzerinteraktion erst endet, dann müsste ich mit jeder Response, die ich vom Server sende, einen neuen Set-Cookie senden. Der jeweils sagt: Jetzt nochmal für die nächsten 30 Minuten. Der andere Punkt kann der sein, wo ich dann möglicherweise eher die Alternative des Expire Werts wählen würde, wenn ich einen festen Zeitpunkt vorgebe und nicht eine relative Zeitangabe. Dass ich möchte, dass ein Cookie sehr viel länger lebt als die Anwendung. Das ist glaube ich eher der Fall, dass ich dann noch Expires wählen würde. Aber eigentlich sind sie da nicht so verschieden. Da kann ich sagen: Das gilt jetzt bis zum Silvester dieses Jahres, dass ich vielleicht das Cookie des Jahres haben möchte, oder ich möchte tatsächlich so eine Remember Me Funktionalität haben, die für ein Jahr gelten sollte. Dann muss ich ein bisschen umrechnen, dass ich bei Max Age rausbekomme, wie denn ein Jahr ist, aber das kriegen wir schon hin, das auszurechnen. Ich habe die Möglichkeit auch deutlich über die Lebensdauer einer Anwendung hinaus ein solches Cookie zu setzen. Natürlich gibt es da immer auch die Möglichkeit, dass der Browser sich entscheidet, da andere Dinge zu tun und trotzdem alle Cookies löscht, in dem Moment, wo die Anwendung beendet wird. Oder maximale Lebensdauer einstellt, die kürzer sind als das, was da ist. Inzwischen ist es auch so, dass die meisten Browser grundsätzlich keine ewig lange geltenden Cookies mehr akzeptieren, sondern für sich alleine festgelegt haben, die Browser Anbieter, dass so ein Cookie, das zehn Jahre lang leben soll, eigentlich keine gute Idee ist und dann einfach so eine Obergrenze einziehen, die man nicht überschreiten kann. Das ist der zeitliche Anwendungsbereich. Der andere ist so: Welcher URIs sollte es eigentlich zutreffen, in welchem Anwendungsbereich?
Till: Eine räumliche.
Stefan: Ja, irgendwie schon eine räumliche, wenn man im Cyberspace denkt.
Till: Grüße an Andi Scheuer.
Stefan: An wen senden wir denn eigentlich diese Cookies? Wir haben zu Anfang gesagt, der Server setzt ein Cookie und an diesen Server sendet der Browser das Cookie wieder zurück. Und das ist tatsächlich die Default Einstellung. Ich schicke mein Cookie genau an den Server, genau die gleiche Authority. Das, was im URI zwischen dem Protokoll und dem Pfad steht und an niemanden sonst schicke ich das gleiche Cookie immer wieder. Das ist die Standardeinstellung. Dann habe ich die Möglichkeit, das vielleicht auf der einen Seite ein bisschen mehr einzuschränken. Es gibt einen optionales Path Attribut, mit dem ich sagen kann: Aber schicke das bitte nur an URIs, die mit einem bestimmten Pfad anfangen. Also nur an /Admin oder so etwas in der Art. Wenn ich also nur das Cookie senden möchte von einen Teilbereich dessen, was auf meinem Server passiert.
Stefan: Das könnte mir zum Beispiel helfen, wenn ich jetzt einfach meine CSS und Bilddateien unter einem anderen Pfad habe und nicht möchte, dass bei jedem dieser Request an meine 1000 Bild und CSS Dateien, die ich jedes Mal laden muss, jedes Mal dieser Cookie dran steht, zum Beispiel.
Stefan: Ja, das wäre natürlich noch viel einfacher, wenn ich sagen könnte, an welche Pfade ich das Cookie nicht senden möchte. Aber leider sieht das die Spezifikation nicht vor. Das heißt, ich muss eher dafür sorgen, dass meine Anwendung, wo ich das Cookie haben möchte, unter einem bestimmten Pfad zu finden ist. Und die Bilder sind dann eben im großen Rest, wo das Cookie nicht ist.
Lucas: Was aber nicht der Realität entspricht. Das kann man festhalten. Das habe ich noch nicht gesehen.
Stefan: Normalerweise machen wir das umgekehrt. Das stimmt wohl. Aber dann hätten wir immer noch die Möglichkeit, die Bilder und die CSS Dateien vielleicht unter einem anderen Host Name anzuwenden. Dann würde das Cookie da nicht hingehen. Es sei denn, ich möchte, dass sie da auch hingehen. Das ist die andere Möglichkeit, so wie ich mit dem Path verschärfen kann, wo das Cookie hingeht, kann ich diese Regel, die Standard Regel aufweichen, indem ich sage: Schicke das doch bitte an alle Hosts, die in einer bestimmten Domain stecken. Da ist die Logik jetzt ein bisschen komplizierter. Man kann nicht sagen: Schickt das an alle .de Server. So große und weiträumig erlaubte Cookies würde kein Browser akzeptieren. Da ist dann ein Konzept von Suffixes im Endeffekt, also von Domains, die zu groß sind. Alles was dahinter kommt ist dann okay. innoq.com kann ich machen. .com kann ich nicht machen. Da gibt es dann bestimmte registrierte Domains und da muss ich immer einen Schritt weiter gehen. Ich muss einen Schritt weiter als .com beispielsweise gehen. Das ist genau die zweite Domain. Es gibt bestimmte Domains, die komplizierter gestaltet sind. Da gibt es das Konzept der registrial Domain, also das: Was kann ich denn tatsächlich kaufen als Domain Namen? Aber da müssen wir jetzt gar nicht so wahnsinnig ins Detail gehen. Ich kann sagen: Schick dieses Cookie bitte an alle Server Namen, die sich unterhalb dieser Domain befinden, die ich da angegeben habe. Dann sollte ich mir allerdings sicher sein, dass ich die auch wirklich alle kontrollieren kann. Wenn irgendjemand in der Lage wäre, einen Server einzusetzen, der unterhalb dieser Domain zu finden ist, ein böser Angreifer, dann würde auch der diese Cookies zu Gesicht bekommen. Dann sollte ich da zumindest nicht wirklich geheime Dinge unverschlüsselt reinschreiben. Verschlüsselt wäre dann vielleicht noch so ein Aspekt, wenn ich dem anderen Server nicht vertrauen kann. Aber so glaube ich mir selbst nicht, dass es eine gute Anwendung ist. Nun waren wir irgendwie schon hinten im Pfad und wir waren beim Authority Teil des URI. Und so ein bisschen geht es auch in den Protokollbereich, das Attribute Secure heißt und an ein Cookie reist: Dieses Cookie sendest du bitte nur, wenn HTTPS verwendet wird. Sende es nicht, wenn wir unverschlüsseltes HTTP senden. Und das kann ich darüber steuern. Dann wird es aber auch tatsächlich vom Browser nur an die HTTPS Requests gesendet. Das ist vielleicht auch eine Möglichkeit, um Bilder zu trennen, dass ich meine Bilder unverschlüsselt ausliefere. Dann würden die das Cookie auch nicht bekommen. Weiß ich aber auch nicht, ob das so eine schlaue Idee ist. Aber ich würde auf jeden Fall Cookies, von denen ich unter gar keinen Umständen möchte, dass sie in die falschen Hände geraten, bei einem Man in the middle mitgelesen werden, auf Secure setzen. Faktisch sollten wir heute sowieso nur noch verschlüsselt reden und Secure sollte eigentlich der Default sein, ist es aber nicht. Das heißt, wenn wir Cookies setzen, wenn wir ein Set-Cookie schreiben, dann sollten wir für uns den mentalen Merker machen, dran denken, Daten Secure daran zuschreiben.
Till: Wenn man jetzt aus einer Weltall Perspektive über die Cookie Spezifikationen browst, gibt es dann noch ein weiteres Flag, das heißt HttpOnly. Da könnte man jetzt denken, das ist eigentlich das Gegenteil von Secure oder dass Secure sowas ist wie HTTPSOnly, dann wäre HttpOnly, dass es nur unverschlüsselt übersendet wird. Das ist aber natürlich Unsinn. Was hat es denn damit genau auf sich?
Lucas: Grundsätzlich gibt es keine Möglichkeit zu sagen: Bitte schicke das niemals über HTTPS. Ich glaube, dafür würden auch Use Cases gesucht werden. HttpOnly ist aber was anderes. Da geht es tatsächlich um JavaScript. Grundsätzlich, wenn wir den Cookie so setzen, wie wir es bisher beschrieben haben, kann ich mit Document.cookie in JavaScript einfach den Cookie auslesen. Jetzt sagen wir mal, wir machen das so, wie der Till gesagt hat. Wir signieren den Cookie und schreiben dahin: User = Till und darunter die Signatur. Dann kann man sich jetzt erst mal nicht ausdenken, dass man auf einmal der Lucas ist. Aber ich könnte mit JavaScript, wenn ich jetzt die Möglichkeit hätte, auf dem Rechner von Till JavaScript auszuführen, in diesem Browser Kontext, dann könnte ich diesen Cookie auslesen und ihn einfach an meinen Server schicken. Und danach könnte ich auch, obwohl es signiert ist, der Till werden. Ich könnte einfach den Cookie klauen. Und um das zu verhindern, kann ich das HttpOnly Flag setzen. Wenn ich das setze, dann ist es nicht mehr möglich über Dokument.cookie diesen Cookie auszulesen. Jetzt könnte man sagen: Ist das nicht blöd, wenn ich beispielsweise aus JavaScript heraus ein HTTP request an den Server schicken will: Ich will jetzt noch Jason runterladen oder sonst irgendwas. Geht das dann überhaupt noch? Ja, das geht trotzdem. Weil wenn ich jetzt beispielsweise in JavaScript die Fetch API benutze oder auch die alte XHR API, um ein Request an den Server zu schicken, dann schickt mein Browser den automatisch schon mit. Das heißt, ich muss dafür gar nichts tun. Das heißt also nicht, dass ich nicht mehr aus JavaScript heraus Requests an meinen Server schicken kann, sondern es heißt wirklich nur, dass ich aus JavaScript heraus nicht selbst auslesen kann. Das heißt, aus meiner Sicht für ein Cookie, der für Authentifizierung oder so etwas benutzt wird, sollte diese Flag immer gesetzt sein, weil es keinen Grund gibt, warum man die aus JavaScript heraus auslesen soll. Wenn man aber jetzt über sowas redet wie den Dark Mode, dann könnte man sagen: Hier, ich möchte gerne den Dark Mode Cookie setzen, da macht dieser Flag vielleicht keinen Sinn, weil dann könnte ich auch ein kleines JavaScript schreiben, was den toggled. Da kann er einfach selbst den Cookie verändern. Dafür muss der Server eigentlich nichts für tun.
Till: Du hast bisher immer gesagt, es wäre lesend. Das ist auch schreibend. Sowohl lesend als auch schreibend.
Lucas: Absolut, genau. Ich könnte also selbst für mich ein Cookie setzen, wie zum Beispiel diesen Dark Mode Cookie. Das wäre aus meiner Sicht vor allem aus dem Bereich, wenn wir jetzt noch mal an diese Kategorien denken, wäre das vor allem diese UI Einstellung, würde ich sagen, wo man durchaus darüber nachdenken kann, ob man die aus JavaScript heraus setzen möchte, wohingegen der ganze Bereich Benutzeridentifikation definitiv was ist, wo man das nicht tun sollte, weil das ein Sicherheitsbedenken ist und es eigentlich auch keinen guten Grund dafür gibt, weil der Browser das eh schon selbst tun kann, dann müssen wir das auch nicht ausschließen. Es gibt auch keinen guten Grund dafür, diese Information selbst von Hand auszulesen.
Till: Wobei wir jetzt hier auch nicht im INNOQ Security Podcast sind. Aber trotzdem muss man natürlich hier an der Stelle auch nochmal festhalten. Stefan ist auch Teil von dem Podcast, dass irgendein JavaScript auf meiner Seite läuft, welches in der Lage ist, irgendwelche Informationen abzugreifen und dann einen Server zu schicken, ohne dass sich das mitkriege und ich eh ein ziemlich großes Problem habe und meine Seite abschalten soll. Das müssen wir auch auf jeden Fall, selbst im nicht Security Podcast noch dazu sagen. Es gibt noch ein letztes Flag und das ist das SameSite Flag. Was hat es denn damit auf sich, Lucas. Das ist jetzt eine große Frage.
Lucas: Wir haben jetzt irgendwie darüber geredet. Eigentlich im ganzen Podcast ging es vor allem um: Mein Server schickt ein Cookie an mein Client und immer wenn ich mit dem gleichen Server spreche, kriege ich den wieder. Stefan hat das schon ein bisschen relativiert. Man könnte auch für 5 Domains diesen Cookie setzen, dass sie bei 5 Domains mitgeschickt wird. Das wäre auch möglich. Aber es ist die Frage: Was passiert, wenn man über die Grenzen von einer Seite hinausgeht? Was ist, wenn ich von einer anderen Seite komme und auf diese Seite geschickt werde? Soll dann der Cookie mitgeschickt werden? Soll dann der Cookie dabei sein oder nicht? Wenn ich zum Beispiel von Google Suchergebnissen auf Amazon.com komme, soll ich dann als Lucas per Default eingeloggt sein oder nicht? Dann wäre das erstmal so, dass ich dann eingeloggt bin. Wenn ich das aber mit dem SameSite Attribut auf strict setze, wäre das tatsächlich nicht so. Dann wäre es so: Ich klicke auf den Link auf Google.com, dort auf den Link zu Amazon und dann sehe ich die Warenseite, aber bin ausgeloggt. Und erst wenn ich dann einmal Reload mache oder auf irgendetwas anderes innerhalb der Amazon Seite klicke, dann bin ich eingeloggt. Das wäre SameSite=Strict. SameSite=Lax ist mittlerweile das, was Default ist in den modernen Browsern. Da wäre es so, dass in diesem Fall der Cookie mitgeschickt würde, weil die Cookies immer dann mitgeschickt werden, wenn es ein GET Request ist, aus einem anderen Kontext herausgelöst wurde. Aber wenn ich zum Beispiel POST Request von irgendwo anders her schicke, dann wäre das verboten. Da kommen wir wieder in den Security Bereich, wo wir überlegen müssen: Welche Sachen sollen möglich sein, welche Sachen sollen nicht möglich sein? Aber wir kommen auch ganz klar in den Bereich Third Party Cookies. Wir wollen irgendwie ein Cookie haben, den ich an einer Stelle setze für einen anderen Kontext. Da haben wir gesagt, das wollen wir heute ein bisschen ausklammern das Thema, weil sonst wird der Podcast zu groß. Dazu würden wir da noch mal ein anderes Thema machen und dann noch mal über Third Party Cookies sprechen. Aber grundsätzlich sollten wir den SameSite meiner Meinung nach zumindest mal auf Lax setzen, auch wenn es Default ist, aber für die Browser, wo es noch nicht der Default ist, weil das erst mal eine gute Security Einstellung ist, um dafür zu sorgen, dass sich andere Leute im Namen von meinen Usern Aktionen durchführen können.
Till: Das hebelt zum Beispiel auch, das alte See-SURF Angriffsmuster aus, was genau auf dem Anschreiben von Cookies in irgendwelchen Kontexten basiert. Aber im Prinzip merkt man schon, das wird jetzt gerade beliebig kompliziert. Das heißt, dieses Third Party Thema ist eins, was uns sehr stark bewegt. Das kann ich für uns alle drei behaupten, weil sich auch gerade da wahnsinnig viel tut. Das heißt, in den Browsern gibt es sehr, sehr viel Bewegung in dem Bereich, dass Browser Hersteller für ihre Nutzer immer wieder entscheiden, ob sie irgendein Recht haben das Cookie in einem bestimmten Kontext mitzuschicken oder nicht. Und das variiert von Browser zu Browser. Und das hebelt auch althergebrachte Muster, die eigentlich ganz wunderbar gültig sein sollten, sowas wie Single sign-on an einigen Stellen aus. Und deswegen haben wir gedacht, wir machen darüber noch mal demnächst, was auch immer das heißen mag, vielleicht oder wahrscheinlich in derselben Runde auch noch mal eine kleine Podcast Folge und reden mal über die Entwicklung und was Third Party Cookies eigentlich genau sind. Was sind eigentlich genau die Szenarien, wo man das macht? Warum tut man das eigentlich? Gibt es gute Gründe dafür? Gibt es auch düstere Gründe dafür? Das würde ich aber dann demnächst machen. Falls ihr nicht noch irgendwas zu sagen habt, bedanken wir uns oder ich bedanke mich zuerst einmal für die Aufmerksamkeit und für die Antworten und Diskussion mit meinen Gesprächspartnern. Und dann hören wir uns nächstes Mal. Vielen Dank.