Abschnitt 12 · Lektion 2
Cookies
In der letzten Lektion hast du gesehen, dass der Server zwischen zwei Anfragen nichts behält. Jetzt die erste der beiden Antworten darauf: Was er nicht behalten kann, lässt er sich vom Browser aufbewahren.
Hin und zurück
<?php
// Diese Seite legt beim ersten Aufruf ein Cookie an und erkennt dich
// beim naechsten daran wieder.
//
// Wichtig ist die Reihenfolge: $_COOKIE ist das, was der Browser
// MITGESCHICKT hat. setcookie() bittet ihn um etwas fuer BEIM
// NAECHSTEN MAL. Deshalb steht beim ersten Aufruf noch nichts drin.
$schonDa = isset($_COOKIE["besuch"]);
setcookie("besuch", "1", ["path" => "/", "httponly" => true, "samesite" => "Lax"]);
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Wiedererkennen</title>
</head>
<body>
<h1><?= $schonDa ? "Wir kennen uns schon" : "Guten Tag, das erste Mal hier?" ?></h1>
<p>Der Browser hat mitgeschickt:
<code><?= htmlspecialchars($_SERVER["HTTP_COOKIE"] ?? "(nichts)") ?></code></p>
<p><a href="/">Noch einmal aufrufen</a></p>
</body>
</html> Ein Cookie ist ein Name mit einem Wert. Du schickst ihn mit setcookie() an den Browser, der
Browser legt ihn ab, und ab da hängt er ihn an jede weitere Anfrage an deine Seite. Du liest ihn
mit $_COOKIE["name"], so wie du in Abschnitt 11 $_POST gelesen hast.
Die Reihenfolge ist die Stelle, an der sich alle einmal wundern. $_COOKIE enthält, was der Browser
bei dieser Anfrage mitgeschickt hat. setcookie() bittet ihn um etwas für die nächste.
Beide haben mit demselben Cookie zu tun und meinen doch zwei verschiedene Zeitpunkte.
Daraus folgt: Ein gerade gesetztes Cookie steht im selben Lauf noch nicht in $_COOKIE. Wer das
nicht weiß, schreibt eine Zeile, prüft sie direkt darunter, bekommt nichts und sucht den Fehler an
der falschen Stelle. Im Beispiel siehst du es an der Überschrift: Beim ersten Aufruf steht dort die
Frage, beim zweiten der Wiedererkennungssatz. Und im Reiter Netzwerk siehst du beide
Richtungen: Bei der ersten Anfrage steht Set-Cookie: besuch=1 unter den Antwort-Kopfzeilen (mit
ein paar Angaben mehr, als du geschrieben hast, dazu gleich mehr), bei der zweiten steht
cookie: besuch=1 unter den Anfrage-Kopfzeilen. Erst hin, dann zurück.
Und weil setcookie() eine Kopfzeile schreibt, gilt dieselbe Regel wie bei header() in Lektion
10.4: Der Aufruf muss hinaus, bevor das erste Zeichen der Seite unterwegs ist. Er gehört in den
PHP-Block ganz oben.
Die vier Angaben, die heute dazugehören
<?php
// Vier Cookies mit vier verschiedenen Angaben, und darunter die
// Kopfzeilen, die dein Code daraus gemacht hat. So sieht ein Cookie
// aus, wenn es die Leitung entlanggeht.
setcookie("nackt", "1");
setcookie("mitpfad", "2", ["path" => "/"]);
setcookie("gesichert", "3", [
"path" => "/",
"secure" => true,
"httponly" => true,
"samesite" => "Lax",
]);
setcookie("mitablauf", "4", [
"expires" => time() + 60 * 60 * 24 * 7,
"path" => "/",
]);
// Die Liste wird HIER gelesen, vor dem ersten Zeichen der Seite. Sobald
// die Seite anfaengt, sind die Kopfzeilen verschickt.
$gesetzt = array_filter(headers_list(), fn($kopf) => stripos($kopf, "Set-Cookie:") === 0);
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Kopfzeilen</title>
<style>
body { font-family: system-ui, sans-serif; }
li { margin-bottom: .6rem; }
code { background: #eee; padding: .1rem .3rem; }
</style>
</head>
<body>
<h1>Das schickt dein Code hinaus</h1>
<ul>
<?php foreach ($gesetzt as $kopf) { ?>
<li><code><?= htmlspecialchars($kopf) ?></code></li>
<?php } ?>
</ul>
</body>
</html> setcookie("name", "wert") funktioniert und ist trotzdem nicht das, was du schreiben willst. Die
Angaben stehen in einem Array hinter dem Wert, und vier davon sollten fast immer dabei sein.
path sagt, für welche Adressen der Browser das Cookie wieder mitschickt. "/" heißt „für die
ganze Seite” und ist fast immer richtig. Ohne die Angabe nimmt der Browser das Verzeichnis der
aktuellen Seite, und dann fehlt das Cookie plötzlich auf einer Unterseite.
httponly verbietet dem JavaScript im Browser, das Cookie zu lesen. Das klingt nach einer
Kleinigkeit und ist die wichtigste Angabe von allen: Schafft es fremdes Skript auf deine Seite, kann
es damit die Kennung deiner angemeldeten Besucher nicht abgreifen. Warum fremdes Skript überhaupt
auf deine Seite kommt, war Lektion 11.4, und Abschnitt 15 geht dem nach.
secure heißt „nur über eine verschlüsselte Verbindung”. Ohne die Angabe schickt der Browser
das Cookie auch über eine unverschlüsselte, und dann kann jeder im selben Netz mitlesen.
samesite entscheidet, ob der Browser das Cookie auch dann mitschickt, wenn die Anfrage von
einer fremden Seite ausgelöst wurde. "Lax" ist der gute Vorgabewert: bei einem normalen Klick
auf einen Link ja, bei einem abgeschickten Formular von woanders nein. Das ist die halbe Miete gegen
einen Angriff, den du in Abschnitt 15 kennenlernst.
Dazu kommt expires, die Laufzeit. Ohne die Angabe ist es ein Sitzungscookie: Es lebt, bis der
Besucher den Browser schließt. Mit time() + 60 * 60 * 24 * 7 lebt es eine Woche. Das zweite
Beispiel liest die Kopfzeilen vor, die dein Code erzeugt hat, und du siehst auf der Seite, was aus
deinen Angaben wird, und dass PHP bei einer Laufzeit gleich zwei Schreibweisen dafür hinausschickt.
Gelesen wird die Liste im PHP-Block oben, bevor die Seite anfängt; was dort steht, ist genau das,
was dein Code hinausschickt.
Eine Regel, die du hier gerade live siehst
Die Seite im Browser läuft in einem Rahmen, und der Rahmen liegt auf einer anderen Adresse als die
Seite drumherum. Für einen Browser ist das genau der Fall, den samesite meint: eine fremde Seite.
Ein Cookie mit Lax käme dort nie an, und dann stünde in dieser Lektion eine Behauptung, die du
nicht nachprüfen könntest.
Deshalb schreibt die Lernplattform jede Set-Cookie-Zeile um, bevor sie in den Rahmen geht:
samesite auf None, dazu Secure, weil None ohne Secure nicht erlaubt ist, und
Partitioned, damit der Browser das Cookie in einer eingebetteten Seite überhaupt noch annimmt. Du
kannst das nachsehen: Im Reiter Netzwerk trägt unter den Antwort-Kopfzeilen jedes Cookie diese
drei Angaben, auch nackt=1 aus dem zweiten Beispiel, dem du gar keine mitgegeben hast. Die Seite
des Beispiels zeigt dagegen deine eigenen Zeilen, weil sie ihre Liste liest, bevor irgendetwas
hinausgeht. Und in der Prüfliste steht ebenfalls genau das, was dein Code schreibt: Der Prüfserver
hat keinen Rahmen und bekommt keine Sonderbehandlung. Auf deinem eigenen Server gilt sowieso, was
du schreibst.
Was ein Cookie nicht ist
<?php
// Diese Seite macht den Fehler, den man nie machen darf: Sie legt
// eine Berechtigung in ein Cookie und glaubt ihr danach.
//
// Klick im Browser auf den zweiten Link. Du bist Chef, ohne dass dich
// jemand gefragt hat. Genau das kann jeder Besucher auch, und zwar
// in den Entwicklerwerkzeugen seines Browsers.
if (isset($_GET["rolle"])) {
setcookie("rolle", $_GET["rolle"], ["path" => "/"]);
header("Location: /", true, 302);
exit;
}
$rolle = $_COOKIE["rolle"] ?? "gast";
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Rollen</title>
</head>
<body>
<h1>Angemeldet als: <?= htmlspecialchars($rolle) ?></h1>
<?php if ($rolle === "chef") { ?>
<p>Umsatz des Monats: 84.120 Euro</p>
<?php } else { ?>
<p>Du siehst hier nur das, was alle sehen.</p>
<?php } ?>
<p>
<a href="/?rolle=gast">Als Gast</a> ·
<a href="/?rolle=chef">Als Chef</a>
</p>
</body>
</html> Ein Cookie liegt beim Besucher. Er kann es lesen, ändern, löschen und erfinden, und dafür braucht er kein Werkzeug außer dem, was in jedem Browser eingebaut ist.
Das dritte Beispiel macht deshalb einen Fehler vor, den man in freier Wildbahn regelmäßig findet:
Es legt eine Rolle in ein Cookie und glaubt ihr danach. Klick im Browser auf „Als Chef”, und du
siehst den Umsatz. Im Reiter Netzwerk siehst du, wie wenig dazu nötig war: eine Antwort mit
Set-Cookie: rolle=chef, und schon die nächste Anfrage trägt cookie: rolle=chef. Der Link ist
nur bequemer als der Weg über die Entwicklerwerkzeuge, gefährlich ist er nicht: Genau dasselbe kann
jeder Besucher von Hand.
Daraus folgt die Regel für den Rest des Kurses: In ein Cookie gehört nichts, worauf du dich verlassen musst. Eine Sprachwahl, ein zugeklapptes Hinweisfeld, ein dunkles Farbschema: gerne. Eine Berechtigung, ein Preis, ein „ist bezahlt”: nie.
Wo das trotzdem hin soll, steht in der nächsten Lektion. Dort bekommt der Besucher nur noch eine Nummer, und alles Wichtige bleibt bei dir.
Zum Mitnehmen
Ein Cookie ist ein Zettel, den dein Server dem Browser des Besuchers in die Hand drückt und den der Browser danach bei jeder Anfrage von selbst wieder vorzeigt. Alles, was daran kompliziert wirkt, folgt aus einem einzigen Satz: Der Zettel liegt auf einem Rechner, der dir nicht gehört.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.