Navigation
 Startseite
 Fachbücher
 Anzeigenmarkt
 Forum
 Webmaster News
 Script Newsletter
 Kontakt
 Script Installation
 Php
 Php Tutorials
 Webhoster Vergleich
 Impressum

Community-Bereich
 kostenlos Registrieren
 Anmelden
 Benutzerliste

Script Datenbank
 Script Archiv
 Script Top 20
 Screenshots
 Testberichte

Suche
 

Unsere Php Scripts
 Counter Script
 Umfrage Script
 Bilder Upload Script
 Terminverwaltung
 Simple PHP Forum
 RSS Grabber

Tools und Generatoren
 .htpasswd Generator
 md5 Generator
 base64 Generator
 Markdown to HTML
 Colorpicker
 Unix timestamp Tool
 Unit Test Generator
 TLD Liste
 Webkatalog‑Verzeichnis

Hosterplus.de
Bekommen Sie Speicherplatz (Webspace), Domains...
https://www.Hosterplus.de
Artfiles.de
Bietet Serviceorientierte...
https://www.Artfiles.de
 
 
 

PHP Sessions

Sie befinden sich: Home > Php > PHP Sessions

In diesem Tutorial möchte ich Ihnen erklären, wie Sie Sessions in PHP benutzen können. Die Sessions werden regelmäßig dazu verwendet, um einen Memberbereich oder Administrationsbereich zu realisieren. Da nur der User einen Zugriff auf die Session bekommt, dem die Session ID bekannt ist. Dadurch ist es möglich individuellen Inhalt für einen User bereitzustellen oder diesen mit einem Passwortschutz zu versehen. In PHP wird über den Befehl session_start() eine neue Session generiert.

Wird den Befehl session_start() die Session ID übergeben, greift der Server auf die hinterlegte Datei zu. Wenn eine Session generiert wird, wird ein Text File unter dem session.save_path generiert. Den Session Save_Path kann man mit dem Befehl session_save_path() ändern. Dazu muss der Befehl vor session_start() ausgeführt werden. Dieses Text File wird unter den Dateinamen Prefix „sess_“ und 32 zufällige Zeichen gespeichert. Die 32 Zeichen ist die aktuelle Session ID, dadurch weiß der Server, welche Datei genutzen werden soll. In diesem Text File werden alle Variablen serialsiert gespeichert, die in der aktuellen Session generiert wurden.

Eine Session ist standardmäßig 24 Minuten (1440 Sekunden) aktiv, wenn bis dahin die Session nicht wieder benutzen wurde, wird standardmäßig das Text File der Session gelöscht. Dies ist abhängig von der Einstellung in der php.ini unter session.gc_maxlifetime. Der Wert session.gc_maxlifetime definiert die Lebensdauer der Session-Daten in Sekunden. Man kann auf Wunsch mit ini_set('session.gc_maxlifetime', $sekunden) diesen Wert ändern.

So nun ein Beispiel zum Starten einer Session:

<?php
session_start();
?>

Der Befehl session_start() sollte immer in der ersten Zeile eines PHP Scriptes stehen. Der Befehl session_start() gibt true oder false zurück. Sollte die Session nicht gestartet werden können, wird ein false zurückgegeben. Zusätzlich muss der Befehl vor dem Senden des Http Header vom Dokument ausgeführt werden, da ansonsten eine entsprechende Fehlermeldung kommt und nicht auf die Session zugegriffen werden kann. Daher ist es empfehlenswert, mit dem Befehl headers_sent() zu prüfen, ob der http Header bereits gesendet wurde.

 

Früher haben wir an dieser Stellen Sie empfohlen, ein @-Zeichen vor session_start() zu schreiben. Dies gilt aber, als eine unsaubere Lösung ohne den Fehler ordentlich zu behandeln. Daher empfiehlt sich bei ein Entwicklungssystem kein @-Zeichen zu verwenden. Da dies die Fehlerausgabe unterbindet.

Generell gilt die Regel bei Produktivsystemen, es dürfen keine Fehlermeldungen ausgegeben werden. Die Fehlermeldung muss in den Logs geschrieben werden. Um zu prüfen, ob bereits der Header gesendet wurde, empfehle ich eine try, Catch Lösung.

Es empfiehlt sich generell zu prüfen, ob der Header gesendet wurde. Sollte dieser bereits gesendet worden sein, kann man eine throw new Exception() werfen und später die Fehlermeldung ausgeben ohne systemrelevante Informationen.

Beispiel zum Prüfen, ob bereits ein http Header gesendet wurde:

<?php
// prüfen ob der HTTP header bereits gesendet wurde
if (headers_sent() == false) {
// HTTP header wurde noch nicht gesendet
session_start();
}
?>

Nachdem wir nun die Session initialisiert haben, können wir damit beginnen, diese mit Inhalt zu befüllen. Hierzu nutzen wir die Servervariable $_SESSION, in dieser werden sogenannte Array Elemente abgelegt. Wenn wir jetzt ein neuer Schlüssel mit einem Wert generieren möchten, müssen wir folgendermaßen die Servervariable ansprechen.

<?php
if (headers_sent() == false) {
session_start();
/* Nun erstellen wir in der Session ein Schlüssel mit einen Wert */
$_SESSION['username'] = 'Nico Schubert';
}
?>


Wie oben in unserem Beispiel geschrieben, haben wir den Schlüssel „username“ mit dem Wert „Nico Schubert“ in unserer Session Array: $_SESSION gespeichert. Wenn der Browser Cookies zulässt, wird jetzt ein Cookie mit der Session ID gespeichert. Jedoch könnten wir in einer weiteren Seite mit diesem Schlüssel den Inhalt der Session ausgeben. Da nicht immer sichergestellt werden kann, dass die Session ID per Cookie gespeichert wird. Sollte man die Session ID über die Konstante SID oder session_id() per GET oder POST übergeben?

Hier das Beispiel für die Ausgabe: (Anm.: Bitte speichern Sie die Datei mit dem Dateinamen: seite2.php)

<?php
if (headers_sent() == false) {
if (!isset($_GET['PHPSESSID'])) $_GET['PHPSESSID'] = '';
if ($_GET['PHPSESSID'] != '') {
session_id($_GET['PHPSESSID']);
}
session_start();

if (!isset($_SESSION['username'])) $_SESSION['username']
= '';
if ($_SESSION['username'] != '') {
echo 'Ihr Benutzername ist: '.$_SESSION['username'];
} else {
echo 'Es konnte kein Benutzernamer ermittelt werden.';
}
}
?>

Sicherheitshinweis: Das Übernehmen einer Session-ID aus GET-Parametern birgt das Risiko einer Session Fixation-Attacke. In modernen PHP-Anwendungen sollte die Session-ID ausschließlich über Cookies übertragen werden (session.use_only_cookies = 1 in der php.ini). Verwenden Sie zusätzlich session_regenerate_id(true) nach einem erfolgreichen Login.

Erklärung: In der ersten Zeile prüfen wir, ob bereits der HTTP Header gesendet wurde. Wenn dieser noch nicht gesendet wurde, prüfen wir mit isset(), ob die GET Variable: $_GET['PHPSESSID'] gesetzen wurde, ansonsten machen wir diese Variable dem PHP Script bekannt. In der nächsten Zeile prüfen wir, ob die Variable: $_GET['PHPSESSID'] einen Wert enthält. Wenn die Variable einen Wert enthält, setzen wir mit session_id() die gewünschte Session-ID, bevor wir session_start() aufrufen. Wichtig: session_id() muss vor session_start() aufgerufen werden.

Sollte kein Wert in $_GET['PHPSESSID'] enthalten sein, starten wir normal ohne Übergabe der Session ID, die Session. Im nächsten Abschnitt prüfen wir, ob die Variable: $_SESSION['username'] bereits einen Wert enthält, ansonsten machen mir diese Variable den PHP Script bekannt. Nun prüfen wir, ob die Variable: $_SESSION['username'] einen Wert enthält und geben diesen Wert aus.

Hinweis: Man sollte bei diesem Beispiel eine IP Überprüfung einbauen, da ansonsten jeder auf die Session zugreifen kann. Empfehlenswert wäre, die IP-Adresse in der Session zu speichern und dann wieder zu prüfen, ob diese existiert.

Es wird hier erklärt, wie man in PHP eine IP-Adresse ermittelt. Anschließend prüft man mit einer sogenannten IF-Anweisung, ob die IP-Adresse in der Session enthalten ist. Zusätzlich kann man mit session_regenerate_id() auch vereinzelt im Scriptlauf die Session ID mal ändern, dies erhöht die Sicherheit.

Wenn Sie die Sessionvariable wieder löschen möchten, können Sie hierzu unset() oder session_destroy() verwenden. Sie können das gesamte Transport Script hier herunterladen.

Soweit alles klar? Wenn ja, so können Sie hier das nächste Thema anfangen. Dabei geht es darum, einen lokalen Entwicklungsserver einrichten.

session_start() richtig konfigurieren

Wenn Sie mit Sessions in PHP arbeiten, haben Sie wahrscheinlich schon hundertmal session_start() aufgerufen, ohne ihm ein einziges Argument mit auf den Weg zu geben. Das funktioniert, ist aber selten die beste Wahl. Seit PHP 7.0 nimmt die Funktion ein optionales Array entgegen, mit dem Sie Session-Optionen sichekt für den aktuellen Request setzen. Das ist viel komfortabler, als drei Zeilen ini_set davor zu schreiben, und es vermeidet, dass Sie die Optionen vergisst, wenn Sie den Code später in eine andere Umgebung kopierst.

Vor allem die Cookie-Parameter sind sicherheitsrelevant. Drei Schalter solltest Sie in jedem ernsthaften Projekt setzen: cookie_secure, cookie_httponly und cookie_samesite. Der erste sorgt dafuer, dass das Session-Cookie nur über HTTPS uebertragen wird. Der zweite versteckt das Cookie vor JavaScript, sodass ein moegliches XSS-Loch nicht sichekt zum Session-Diebstahl führt. Der dritte schraenkt ein, in welchen Cross-Site-Kontexten der Browser das Cookie überhaupt mitsendet, und ist Ihr bester Freund gegen klassisches CSRF.

<?php
declare(strict_types=1);

session_start([
    'cookie_lifetime' => 0,
    'cookie_path'     => '/',
    'cookie_domain'   => '',
    'cookie_secure'   => true,
    'cookie_httponly' => true,
    'cookie_samesite' => 'Lax',
    'use_strict_mode' => true,
    'sid_length'      => 48,
    'sid_bits_per_character' => 6,
]);

Besondere Aufmerksamkeit verdient use_strict_mode. Steht der Schalter auf true, akzeptiert PHP nur Session-IDs, die der Server selbst generiert hat. Schickt ein Angreifer eine ausgedachte ID per URL oder Cookie, vergibt PHP eine neue. Ohne strict mode kann ein Angreifer dem Opfer eine ID unterschieben und sich damit nach dem Login dieselbe Session teilen. Das nennt sich Session-Fixation und ist mit einem einzigen Schalter erledigt.

Session-Hijacking-Schutz mit session_regenerate_id()

Eine Session-ID ist im Grunde nichts anderes als ein langes Passwort, das in einem Cookie liegt. Wer sie kennt, ist eingeloggt. Damit ein Angreifer mit einer geklauten ID nicht ewig Schaden anrichten kann, solltest Sie die ID an jedem Punkt austauschen, an dem sich die Vertrauensstufe der Session ändert. Der wichtigste dieser Punkte ist der erfolgreiche Login. Sichekt danach gehoert ein Aufruf von session_regenerate_id(true) hin. Der zweite Parameter sorgt dafuer, dass die alte Session-Datei gelöscht wird, anstatt parallel weiterzuleben.

<?php
declare(strict_types=1);

session_start([
    'cookie_secure'   => true,
    'cookie_httponly' => true,
    'cookie_samesite' => 'Lax',
    'use_strict_mode' => true,
]);

function login_user(int $userId): void
{
    session_regenerate_id(true);

    $_SESSION['user_id']   = $userId;
    $_SESSION['logged_in'] = true;
    $_SESSION['login_at']  = time();
}

if (!isset($_SESSION['last_regen']) || time() - $_SESSION['last_regen'] > 900) {
    session_regenerate_id(true);
    $_SESSION['last_regen'] = time();
}

Wenn Sie tiefer in das Thema eintauchen wollen, lohnt sich ein Blick auf session_id(). Dort sehen Sie, wie Sie IDs lesen und gezielt setzen können. Im Alltag bleibst Sie aber besser bei session_regenerate_id(), weil PHP dort automatisch genug Entropie verwendet.

Session-Daten richtig löschen

Das Beenden einer Session ist tueckischer, als es klingt. Viele Anleitungen rufen einfach session_destroy() auf und denken, damit waere alles erledigt. Tatsaechlich löscht die Funktion nur die Session-Daten auf dem Server. Das Cookie im Browser bleibt unverändert, und die Variable $_SESSION ist im aktuellen Request weiterhin gefuellt. Wer es richtig machen will, geht in drei Schritten vor.

Zuerst das Array leeren, dann das Cookie auf der Client-Seite überschreiben und erst danach die Session-Datei zerstoeren. Das Cookie wird mit setcookie() auf einen Zeitpunkt in der Vergangenheit gesetzen, was den Browser anweist, es zu vergessen. Achten Sie darauf, dieselben Parameter zu verwenden, mit denen das Cookie urspruenglich erstellt wurde, sonst löscht der Browser ein anderes Cookie oder gar keins.

<?php
declare(strict_types=1);

function logout_user(): void
{
    $_SESSION = [];

    if (ini_get('session.use_cookies')) {
        $params = session_get_cookie_params();
        setcookie(
            session_name(),
            '',
            [
                'expires'  => time() - 42000,
                'path'     => $params['path'],
                'domain'   => $params['domain'],
                'secure'   => $params['secure'],
                'httponly' => $params['httponly'],
                'samesite' => $params['samesite'],
            ]
        );
    }

    session_destroy();
}

Danach ist es eine gute Idee, den User auf eine neutrale Seite weiterzuleiten und den aktuellen Request mit exit zu beenden. So vermeiden Sie, dass nachfolgender Code aus Versehen wieder auf die geleerten Session-Werte zugreift.

Lifetime und Garbage-Collection verstehen

Sessions leben nicht ewig. PHP raeumt verwaiste Session-Dateien über einen Garbage-Collector auf, der bei jedem session_start() mit einer kleinen Wahrscheinlichkeit anspringt. Gesteuert wird das Verhalten über drei Konfigurationswerte. session.gc_maxlifetime legt fest, nach wie vielen Sekunden eine inaktive Session als veraltet gilt. Die Werte session.gc_probability und session.gc_divisor bestimmen die Wahrscheinlichkeit, dass der GC bei einem Request laeuft.

Die Standardwerte 1 und 100 bedeuten ein Prozent. Auf einer kleinen Seite reicht das vollkommen aus, auf einer Seite mit sehr wenigen Requests kann es sein, dass der GC fast nie laeuft. Auf großen Seiten ist die Wahrscheinlichkeit oft auf 1 zu 1000 gesetzen, weil der GC sonst zu oft I/O-Last produziert. Wichtig ist auch der Standard-Speicherort. Auf vielen Linux-Distributionen wird /var/lib/php/sessions per Cronjob aufgeraeumt, was den eingebauten GC ueberbruecken kann.

<?php
declare(strict_types=1);

ini_set('session.gc_maxlifetime', '14400');
ini_set('session.gc_probability', '1');
ini_set('session.gc_divisor',     '100');

$savePath = __DIR__ . '/var/sessions';
if (!is_dir($savePath)) {
    mkdir($savePath, 0700, true);
}
session_save_path($savePath);

session_start();

Wer absolute Kontrolle über die Lifetime braucht, sollte die GC nicht alleine arbeiten lassen, sondern bei jedem Request einen eigenen Timestamp prüfen. Speichern Sie bei jedem Login einen Wert wie $_SESSION['login_at'] und vergleiche ihn mit time(). Ist die Differenz zu groß, rufen Sie Ihre Logout-Funktion auf.

Custom Session-Handler mit PDO

Standardmaessig speichert PHP Sessions als Dateien auf der Festplatte. Auf einem einzelnen Server reicht das, sobald Sie aber Load-Balancing einsetzen oder Sessions zwischen mehreren Anwendungen teilen wollen, brauchen Sie einen zentralen Speicher. Die Loesung heißt SessionHandlerInterface. Sie implementierst sechs Methoden, registrieren die Klasse mit session_set_save_handler(), und PHP fragt Ihren Handler bei jeder Operation. Eine populaere Variante ist die Speicherung in einer MySQL-Tabelle über PDO.

<?php
declare(strict_types=1);

final class PdoSessionHandler implements SessionHandlerInterface
{
    public function __construct(private readonly PDO $pdo) {}

    public function open(string $path, string $name): bool { return true; }
    public function close(): bool { return true; }

    public function read(string $id): string
    {
        $stmt = $this->pdo->prepare(
            'SELECT data FROM sessions WHERE id = :id AND expires > :now'
        );
        $stmt->execute(['id' => $id, 'now' => time()]);
        return (string) ($stmt->fetchColumn() ?: '');
    }

    public function write(string $id, string $data): bool
    {
        $expires = time() + (int) ini_get('session.gc_maxlifetime');
        $stmt = $this->pdo->prepare(
            'REPLACE INTO sessions (id, data, expires) VALUES (:id, :data, :exp)'
        );
        return $stmt->execute(['id' => $id, 'data' => $data, 'exp' => $expires]);
    }

    public function destroy(string $id): bool
    {
        return $this->pdo->prepare('DELETE FROM sessions WHERE id = :id')
                       ->execute(['id' => $id]);
    }

    public function gc(int $max_lifetime): int|false
    {
        $stmt = $this->pdo->prepare('DELETE FROM sessions WHERE expires < :now');
        $stmt->execute(['now' => time()]);
        return $stmt->rowCount();
    }
}

$pdo = new PDO('mysql:host=localhost;dbname=app;charset=utf8mb4', 'user', 'pass', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

session_set_save_handler(new PdoSessionHandler($pdo), true);
session_start();

Die zugehoerige Tabelle hat drei Spalten: id VARCHAR(128) PRIMARY KEY, data BLOB und expires INT UNSIGNED. Wenn Sie noch mehr Performance brauchen, können Sie die gleiche Logik gegen Redis bauen. Dort tauschst Sie die SQL-Statements gegen SET und GET aus und nutzen das eingebaute TTL-System, was die GC-Methode ueberfluessig macht.

CSRF-Token in der Session

Sessions identifizieren den User, schuetzen aber nicht davor, dass eine fremde Seite im Hintergrund Anfragen an Ihre Anwendung schickt. Genau hier setzen das CSRF-Token-Pattern an. Sie erzeugst pro Session einen kryptografischen Zufallswert, legen ihn in $_SESSION ab und packst ihn als verstecktes Feld in jedes Formular. Beim Verarbeiten vergleichen Sie den Wert aus $_POST mit dem aus der Session. Stimmen sie nicht ueberein, brichst Sie ab.

<?php
declare(strict_types=1);

session_start();

function csrf_token(): string
{
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function csrf_check(string $token): bool
{
    if (empty($_SESSION['csrf_token'])) {
        return false;
    }
    return hash_equals($_SESSION['csrf_token'], $token);
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!csrf_check($_POST['csrf'] ?? '')) {
        http_response_code(403);
        exit('CSRF-Token ungueltig');
    }
}

Zwei Details sind wichtig. Erstens nutzen Sie random_bytes(), weil rand() nicht kryptografisch sicher ist. Zweitens vergleichen Sie mit hash_equals(), das in konstanter Zeit arbeitet. Ein normaler ===-Vergleich kann sonst per Zeitmessung den Token Stueck für Stueck verraten.

Flash-Messages-Pattern

Eine Flash-Message ist eine Nachricht, die genau einen Folge-Request ueberlebt. Klassisches Szenario: Der User schickt ein Formular ab, der Server speichert die Daten und macht einen Resichect auf die Folgeseite. Auf dieser Folgeseite soll die Erfolgsmeldung erscheinen, danach aber nicht mehr. Mit Sessions geht das in wenigen Zeilen, und Sie brauchen kein Framework dafuer.

<?php
declare(strict_types=1);

session_start();

function flash_set(string $key, string $message): void
{
    $_SESSION['_flash'][$key] = $message;
}

function flash_get(string $key): ?string
{
    if (!isset($_SESSION['_flash'][$key])) {
        return null;
    }
    $msg = $_SESSION['_flash'][$key];
    unset($_SESSION['_flash'][$key]);
    return $msg;
}

flash_set('success', 'Dein Profil wurde aktualisiert.');
header('Location: /profile');
exit;

Das Pattern wirkt klein, lohnt sich aber enorm. Ohne Flash-Messages stehst Sie vor der Wahl, die Erfolgsmeldung in die URL zu packen, was schnell unschoen aussieht, oder den Resichect wegzulassen, was dann zum klassischen Doppelt-Absenden-Problem führt. Mit Sessions haben Sie beides gelösen.

Mermaid: Session-Lifecycle

Damit die Reihenfolge der Schritte greifbar wird, hilft eine kleine Skizze. Sie zeigt den typischen Lebenszyklus einer Session, vom anonymen Besuch über Login und Nutzung bis zum Logout. Die Pfeile geben die Standard-Aufrufe an, die Sie in jedem Schritt brauchen.

flowchart TD
    A[Besuch session_start] --> B{Login?}
    B -->|Nein| A
    B -->|Ja| C[regenerate_id user_id setzen]
    C --> D[Nutzung aus SESSION lesen]
    D --> E{Logout?}
    E -->|Nein| D
    E -->|Ja| F[Array leeren Cookie loeschen destroy]

Die drei farblich getrennten Phasen entsprechen exakt drei Funktionen, die Sie in Ihrem Code haben solltest: einen Bootstrap für session_start(), eine login_user()-Funktion und eine logout_user()-Funktion. Wenn Sie die Lifecycle-Skizze als Grundlage nehmen, faellt es leicht, vergessene Schritte aufzudecken.

Praxisbeispiel: vollständiger Login-Flow

Zum Abschluss setzen Sie alle Bausteine zusammen. Das folgende Beispiel zeigt einen kompletten Login-Vorgang mit sicherem Session-Setup, Passwort-Prüfung, ID-Regeneration, CSRF-Schutz und Flash-Message.

<?php
declare(strict_types=1);

session_start([
    'cookie_secure'   => true,
    'cookie_httponly' => true,
    'cookie_samesite' => 'Lax',
    'use_strict_mode' => true,
]);

require __DIR__ . '/db.php';

if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!hash_equals($_SESSION['csrf_token'], (string) ($_POST['csrf'] ?? ''))) {
        http_response_code(403);
        exit('Ungueltiges Token');
    }

    $email = (string) ($_POST['email'] ?? '');
    $pass  = (string) ($_POST['password'] ?? '');

    $stmt = $pdo->prepare('SELECT id, password_hash FROM users WHERE email = :e LIMIT 1');
    $stmt->execute(['e' => $email]);
    $user = $stmt->fetch(PDO::FETCH_ASSOC);

    if ($user && password_verify($pass, $user['password_hash'])) {
        session_regenerate_id(true);

        $_SESSION['user_id']    = (int) $user['id'];
        $_SESSION['logged_in']  = true;
        $_SESSION['login_at']   = time();
        $_SESSION['_flash']['success'] = 'Willkommen zurueck!';

        header('Location: /dashboard');
        exit;
    }

    $_SESSION['_flash']['error'] = 'E-Mail oder Passwort falsch.';
    header('Location: /login');
    exit;
}

Das Beispiel ist bewusst pragmatisch gehalten. In einer größeren Anwendung würden Sie die Bausteine in Service-Klassen auslagern, etwa einen AuthService mit Methoden wie login(), logout() und currentUser(). Das Skelett bleibt aber dasselbe: Session sicher starten, CSRF-Token mitfuehren, Passwort mit password_verify() prüfen, ID nach erfolgreichem Login austauschen, Flash-Message setzen und per Resichect weiterleiten.