Lekcja 17: Sesje i Ciasteczka - Zarządzanie Stanem Użytkownika

Witaj w siedemnastej lekcji kursu PHP! HTTP, protokół na którym opiera się World Wide Web, jest z natury protokołem bezstanowym (stateless). Oznacza to, że każde żądanie od klienta (przeglądarki) do serwera jest traktowane jako niezależna transakcja, a serwer nie pamięta poprzednich interakcji z tym samym klientem. Jednak wiele aplikacji internetowych, takich jak sklepy e-commerce, fora dyskusyjne czy systemy logowania, wymaga utrzymania informacji o stanie użytkownika pomiędzy kolejnymi żądaniami – np. co użytkownik ma w koszyku, czy jest zalogowany, jakie ma preferencje. PHP oferuje dwa główne mechanizmy do zarządzania stanem użytkownika: sesje i ciasteczka (cookies). W tej lekcji dokładnie omówimy oba te mechanizmy, ich działanie, zastosowania oraz aspekty bezpieczeństwa.

Ciasteczka (Cookies)

Ciasteczka to małe fragmenty danych (pliki tekstowe), które serwer wysyła do przeglądarki użytkownika, a przeglądarka przechowuje je na komputerze klienta i odsyła z powrotem do serwera przy każdym kolejnym żądaniu do tej samej domeny. Pozwalają one "zapamiętać" pewne informacje o użytkowniku lub jego aktywności.

Jak Działają Ciasteczka?

  1. Serwer wysyła ciasteczko: Gdy użytkownik odwiedza stronę po raz pierwszy lub wykonuje akcję, która wymaga zapamiętania informacji, serwer może wysłać ciasteczko do przeglądarki za pomocą nagłówka HTTP Set-Cookie.
  2. Przeglądarka przechowuje ciasteczko: Przeglądarka zapisuje ciasteczko na dysku lokalnym użytkownika (lub w pamięci, jeśli to ciasteczko sesyjne bez daty wygaśnięcia).
  3. Przeglądarka odsyła ciasteczko: Przy każdym kolejnym żądaniu do tej samej domeny (i ścieżki, jeśli została określona), przeglądarka automatycznie dołącza odpowiednie ciasteczka do nagłówka HTTP Cookie.
  4. Serwer odczytuje ciasteczko: Skrypt PHP na serwerze może odczytać wartości ciasteczek z superglobalnej tablicy $_COOKIE.

Ustawianie Ciasteczek w PHP - Funkcja setcookie()

W PHP ciasteczka ustawia się za pomocą funkcji setcookie(). Musi ona zostać wywołana przed jakimkolwiek innym wyjściem do przeglądarki (przed jakimkolwiek kodem HTML, echem, spacjami itp.), ponieważ modyfikuje nagłówki HTTP.

Składnia funkcji setcookie():

<?php
bool setcookie (
    string $name, // Nazwa ciasteczka
    string $value = "", // Wartość ciasteczka
    int $expires_or_options = 0, // Czas wygaśnięcia (timestamp Unix) lub tablica opcji (od PHP 7.3)
    string $path = "", // Ścieżka na serwerze, dla której ciasteczko będzie dostępne
    string $domain = "", // Domena, dla której ciasteczko będzie dostępne
    bool $secure = false, // Czy ciasteczko ma być wysyłane tylko przez HTTPS?
    bool $httponly = false // Czy ciasteczko ma być dostępne tylko przez protokół HTTP (niedostępne dla JavaScript)?
);
?>

Parametry:

<?php
// Pamiętaj, że setcookie() musi być wywołane przed jakimkolwiek outputem!

// Ustawienie prostego ciasteczka, które wygaśnie po zamknięciu przeglądarki
setcookie("uzytkownik", "JanKowalski");

// Ustawienie ciasteczka na godzinę
$czas_wygasniecia = time() + 3600; // obecny czas + 1 godzina (3600 sekund)
setcookie("ostatnia_wizyta", date("Y-m-d H:i:s"), $czas_wygasniecia);

// Ustawienie ciasteczka z większą liczbą opcji (od PHP 7.3 używając tablicy opcji)
$opcje_ciasteczka = [
    "expires" => time() + (86400 * 30), // na 30 dni
    "path" => "/",
    "domain" => "", // Pusta wartość oznacza bieżącą domenę
    "secure" => true, // Tylko przez HTTPS
    "httponly" => true, // Niedostępne dla JavaScript
    "samesite" => "Lax" // Ochrona CSRF
];
setcookie("preferencje_koloru", "ciemny_motyw", $opcje_ciasteczka);

// Jeśli używasz PHP < 7.3, opcje przekazujesz jako osobne argumenty:
// setcookie("preferencje_koloru", "ciemny_motyw", time() + (86400 * 30), "/", "", true, true);

echo "Ciasteczka zostały (lub zostaną) ustawione. Odśwież stronę, aby je zobaczyć.";
?>

Odczytywanie Ciasteczek - Superglobalna Tablica $_COOKIE

Wartości ustawionych ciasteczek są dostępne w skrypcie PHP za pośrednictwem superglobalnej tablicy asocjacyjnej $_COOKIE. Tablica ta jest wypełniana na początku działania skryptu, na podstawie ciasteczek przesłanych przez przeglądarkę.

<?php
// Sprawdzenie, czy ciasteczko "uzytkownik" istnieje
if (isset($_COOKIE["uzytkownik"])) {
    $nazwa_uzytkownika = htmlspecialchars($_COOKIE["uzytkownik"]); // Zawsze oczyszczaj dane wyjściowe!
    echo "Witaj ponownie, $nazwa_uzytkownika!<br>";
} else {
    echo "Witaj, gościu! Wygląda na to, że jesteś tu po raz pierwszy (lub ciasteczka są wyłączone/usunięte).<br>";
}

if (isset($_COOKIE["ostatnia_wizyta"])) {
    echo "Twoja ostatnia wizyta (wg ciasteczka): " . htmlspecialchars($_COOKIE["ostatnia_wizyta"]) . "<br>";
}

if (isset($_COOKIE["preferencje_koloru"])) {
    echo "Twoje preferencje koloru: " . htmlspecialchars($_COOKIE["preferencje_koloru"]) . "<br>";
}

// Wyświetlenie wszystkich ciasteczek (do celów debugowania)
// echo "<h3>Wszystkie ciasteczka (".htmlspecialchars("$_COOKIE")."):</h3><pre>";
// print_r($_COOKIE);
// echo "</pre>";
?>

Ważne: Ciasteczka ustawione za pomocą setcookie() w danym skrypcie nie będą dostępne w tablicy $_COOKIE podczas tego samego wykonania skryptu. Będą dostępne dopiero przy następnym żądaniu HTTP, gdy przeglądarka je odeśle.

Modyfikowanie i Usuwanie Ciasteczek

Modyfikowanie ciasteczka odbywa się poprzez ponowne wywołanie setcookie() z tą samą nazwą, ale nową wartością i/lub nowymi opcjami. Wszystkie argumenty (ścieżka, domena itp.) powinny być takie same jak przy oryginalnym ustawieniu, chyba że celowo chcemy je zmienić.

Usuwanie ciasteczka polega na ustawieniu go z datą wygaśnięcia w przeszłości. Najprościej jest użyć time() - 3600 (lub dowolnej innej wartości ujemnej lub mniejszej od obecnego czasu). Należy również podać te same parametry path i domain, które były użyte przy tworzeniu ciasteczka.

<?php
// Załóżmy, że ciasteczko "uzytkownik" już istnieje

// Modyfikacja wartości ciasteczka "uzytkownik"
// setcookie("uzytkownik", "ZmienionyJanKowalski", time() + 3600, "/");

// Usuwanie ciasteczka "uzytkownik"
// Należy podać te same parametry path i domain, co przy tworzeniu
setcookie("uzytkownik", "", time() - 3600, "/"); // Ustawienie wartości na pustą i czas wygaśnięcia w przeszłości

// Usuwanie ciasteczka "ostatnia_wizyta"
setcookie("ostatnia_wizyta", "", time() - 3600, "/");

// Usuwanie ciasteczka "preferencje_koloru" (z opcjami)
$opcje_usuniecia = [
    "expires" => time() - 3600,
    "path" => "/",
    "domain" => "",
    "secure" => true,
    "httponly" => true,
    "samesite" => "Lax"
];
setcookie("preferencje_koloru", "", $opcje_usuniecia);

echo "Ciasteczko \"uzytkownik\" zostało usunięte (lub jego usunięcie zostało zlecone). Odśwież stronę, aby zobaczyć efekt.";

// Po usunięciu, ciasteczko nie będzie już dostępne w $_COOKIE przy następnym żądaniu.
// Aby natychmiast usunąć je z tablicy $_COOKIE w bieżącym skrypcie (tylko dla tego wykonania):
// if (isset($_COOKIE["uzytkownik"])) {
//     unset($_COOKIE["uzytkownik"]);
// }
?>

Zalety i Wady Ciasteczek

Zalety:

Wady:

Sesje (Sessions)

Sesje w PHP to mechanizm pozwalający na przechowywanie informacji o użytkowniku po stronie serwera przez cały czas trwania jego wizyty na stronie (lub dłużej). Zamiast przechowywać wszystkie dane u klienta (jak w ciasteczkach), serwer przechowuje tylko unikalny identyfikator sesji (Session ID) po stronie klienta, zazwyczaj w specjalnym ciasteczku sesyjnym. Dane sesji są przechowywane na serwerze, np. w plikach, w bazie danych lub w pamięci (np. Memcached, Redis).

Jak Działają Sesje?

  1. Rozpoczęcie sesji: Gdy sesja jest inicjowana (za pomocą session_start()), PHP sprawdza, czy klient przesłał identyfikator sesji (np. w ciasteczku).
  2. Generowanie/Odtwarzanie ID Sesji:
    • Jeśli klient nie przesłał ID sesji lub przesłane ID jest nieprawidłowe/wygasłe, PHP generuje nowy, unikalny identyfikator sesji.
    • Jeśli klient przesłał prawidłowe ID sesji, PHP próbuje odtworzyć dane sesji powiązane z tym ID z magazynu po stronie serwera.
  3. Wysyłanie ID Sesji do Klienta: Nowo wygenerowany (lub istniejący) ID sesji jest wysyłany do przeglądarki klienta, najczęściej jako ciasteczko sesyjne (o nazwie zdefiniowanej w session.name, domyślnie PHPSESSID). Może być również przekazywany w URL (choć jest to mniej bezpieczne i niezalecane).
  4. Przechowywanie i Odczyt Danych Sesji: Skrypt PHP może zapisywać i odczytywać dane w superglobalnej tablicy $_SESSION.
  5. Zapis Danych Sesji: Po zakończeniu działania skryptu, PHP automatycznie zapisuje zawartość tablicy $_SESSION do skonfigurowanego magazynu sesji na serwerze.
  6. Kolejne Żądania: Przy kolejnych żądaniach, klient odsyła ID sesji, a PHP używa go do odtworzenia odpowiednich danych sesji z serwera do tablicy $_SESSION.

Rozpoczynanie Sesji - Funkcja session_start()

Funkcja session_start() inicjuje mechanizm sesji. Musi być wywołana na początku każdego skryptu, który ma korzystać z sesji, i przed jakimkolwiek innym wyjściem do przeglądarki (podobnie jak setcookie()).

<?php
// session_start() musi być na samej górze, przed jakimkolwiek outputem
if (session_status() === PHP_SESSION_NONE) { // Sprawdzenie, czy sesja nie została już uruchomiona
    session_start();
}

// Teraz można pracować z tablicą $_SESSION
echo "Sesja została uruchomiona. ID Sesji: " . session_id();
?>

Od PHP 5.4.0 session_status() może być użyte do sprawdzenia statusu sesji: PHP_SESSION_DISABLED, PHP_SESSION_NONE (sesje włączone, ale żadna nieaktywna), PHP_SESSION_ACTIVE.

Praca z Danymi Sesji - Superglobalna Tablica $_SESSION

Po wywołaniu session_start(), można zapisywać i odczytywać dane w superglobalnej tablicy asocjacyjnej $_SESSION.

<?php
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// Zapisywanie danych do sesji
$_SESSION["username"] = "AlicjaWkrainiePHP";
$_SESSION["user_id"] = 123;
$_SESSION["licznik_odwiedzin"] = isset($_SESSION["licznik_odwiedzin"]) ? $_SESSION["licznik_odwiedzin"] + 1 : 1;

// Odczytywanie danych z sesji
if (isset($_SESSION["username"])) {
    echo "Zalogowany użytkownik: " . htmlspecialchars($_SESSION["username"]) . "<br>";
}

echo "Odwiedziłeś tę stronę (w tej sesji) " . $_SESSION["licznik_odwiedzin"] . " raz(y).<br>";

// Wyświetlenie całej tablicy $_SESSION (do celów debugowania)
// echo "<h3>Zawartość ".htmlspecialchars("$_SESSION").":</h3><pre>";
// print_r($_SESSION);
// echo "</pre>";
?>

Kończenie Sesji i Usuwanie Danych

Aby usunąć poszczególne zmienne sesyjne, można użyć unset():

<?php
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// Usunięcie konkretnej zmiennej sesyjnej
if (isset($_SESSION["user_id"])) {
    unset($_SESSION["user_id"]);
}
?>

Aby usunąć wszystkie dane sesji (opróżnić tablicę $_SESSION), można użyć session_unset():

<?php
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

session_unset(); // Usuwa wszystkie zmienne z $_SESSION
?>

Aby całkowicie zniszczyć sesję (usunąć dane sesji z serwera oraz unieważnić ID sesji), używa się session_destroy(). Po jej wywołaniu, tablica $_SESSION staje się pusta, a przy następnym żądaniu (jeśli sesja zostanie ponownie uruchomiona) zostanie wygenerowane nowe ID sesji.

<?php
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// Najpierw usuń wszystkie zmienne sesyjne
session_unset();

// Następnie zniszcz sesję
if (session_destroy()) {
    echo "Sesja została pomyślnie zniszczona.<br>";
} else {
    echo "Nie udało się zniszczyć sesji.<br>";
}

// Dodatkowo, warto usunąć ciasteczko sesyjne po stronie klienta, aby zapewnić pełne "wylogowanie"
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(session_name(), 
              ", 
              time() - 42000, // Czas w przeszłości
              $params["path"],
              $params["domain"],
              $params["secure"],
              $params["httponly"]
    );
    echo "Ciasteczko sesyjne zostało usunięte (zlecone usunięcie).<br>";
}

// Po session_destroy(), $_SESSION jest pusta, a ID sesji jest unieważnione.
// Próba dostępu do $_SESSION["username"] teraz dałaby błąd (Undefined array key) lub null.
?>

Konfiguracja Sesji w PHP (php.ini lub funkcje ini_set())

Zachowanie sesji można konfigurować za pomocą wielu dyrektyw w php.ini lub dynamicznie za pomocą ini_set() (przed wywołaniem session_start()). Najważniejsze z nich to:

Przykład zmiany konfiguracji przed session_start():

<?php
ini_set("session.cookie_httponly", 1);
ini_set("session.use_strict_mode", 1);
ini_set("session.gc_maxlifetime", 1800); // Sesja wygasa po 30 minutach nieaktywności

if (session_status() === PHP_SESSION_NONE) {
    session_start();
}
// ... reszta kodu sesji ...
?>

Bezpieczeństwo Sesji

Bezpieczeństwo sesji jest kluczowe, zwłaszcza w systemach logowania i aplikacjach przetwarzających wrażliwe dane.

  1. Regeneracja ID Sesji (session_regenerate_id()): Aby zapobiec atakom typu Session Fixation (ustalenie sesji), należy regenerować ID sesji po każdej istotnej zmianie stanu, np. po zalogowaniu użytkownika lub zmianie poziomu uprawnień. Funkcja session_regenerate_id(true) generuje nowe ID i usuwa stary plik sesji.
  2. <?php
        // Po pomyślnym zalogowaniu użytkownika:
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
        // ... logika weryfikacji loginu i hasła ...
        if ($uzytkownik_poprawnie_zalogowany) {
            session_regenerate_id(true); // Regeneruj ID sesji, usuwając stare
            $_SESSION["zalogowany"] = true;
            $_SESSION["user_id"] = $id_pobrany_z_bazy;
        }
        ?>
        
  3. Używaj HTTPS: Zawsze używaj HTTPS, aby szyfrować komunikację między klientem a serwerem, w tym przesyłanie ID sesji w ciasteczku. Ustaw session.cookie_secure = On.
  4. HttpOnly i Secure dla Ciasteczek Sesyjnych: Ustaw session.cookie_httponly = On i session.cookie_secure = On.
  5. SameSite dla Ciasteczek Sesyjnych: Rozważ ustawienie session.cookie_samesite na "Lax" lub "Strict".
  6. session.use_strict_mode = On: Zwiększa bezpieczeństwo przed session fixation.
  7. Nie Przechowuj Wrażliwych Danych Bezpośrednio w $_SESSION, jeśli nie jest to konieczne: Jeśli przechowujesz np. ID użytkownika, to wystarczy. Unikaj przechowywania haseł (nawet zahashowanych) czy numerów kart kredytowych w sesji.
  8. Waliduj Dane Użytkownika w Sesji: Nie ufaj ślepo danym odczytanym z sesji, zwłaszcza jeśli mogły zostać ustawione na podstawie wcześniejszego, potencjalnie zmodyfikowanego, wejścia użytkownika.
  9. Ustawiaj Odpowiedni Czas Życia Sesji (session.gc_maxlifetime): Zbyt długi czas życia nieaktywnej sesji zwiększa ryzyko jej przejęcia.
  10. Zapewnij Mechanizm Wylogowania: Zawsze implementuj funkcję wylogowania, która niszczy sesję (session_unset(), session_destroy()) i usuwa ciasteczko sesyjne.
  11. Ochrona przed Session Hijacking (Przejęciem Sesji): Dodatkowe techniki mogą obejmować sprawdzanie User-Agenta przeglądarki lub adresu IP powiązanego z sesją (choć to może być problematyczne dla użytkowników z dynamicznym IP lub za proxy).

Zalety i Wady Sesji

Zalety:

Wady:

Ciasteczka vs. Sesje - Kiedy Czego Używać?

Cecha Ciasteczka (Cookies) Sesje (Sessions)
Przechowywanie danych Po stronie klienta (w przeglądarce) Po stronie serwera
Przesyłane dane Cała zawartość ciasteczka z każdym żądaniem Tylko ID sesji (zazwyczaj w ciasteczku)
Rozmiar danych Mały (ok. 4KB na ciasteczko, limit liczby) Duży (ograniczony zasobami serwera)
Bezpieczeństwo danych Niższe (widoczne i modyfikowalne przez klienta) Wyższe (dane na serwerze, klient ma tylko ID)
Trwałość Mogą być trwałe (ustawiony czas wygaśnięcia) lub sesyjne Zazwyczaj trwają do zamknięcia przeglądarki lub wygaśnięcia na serwerze (gc_maxlifetime)
Zastosowania Zapamiętywanie preferencji (np. motyw strony), śledzenie (za zgodą), małe, niewrażliwe dane Logowanie użytkowników, koszyki zakupowe, przechowywanie stanu aplikacji, wrażliwe dane tymczasowe
Zależność Użytkownik może wyłączyć ciasteczka Zazwyczaj polegają na ciasteczkach do przechowywania ID sesji (choć możliwe jest przekazywanie ID w URL)

Generalna zasada:

Podsumowanie Lekcji

W tej lekcji nauczyliśmy się, jak zarządzać stanem użytkownika w PHP za pomocą ciasteczek i sesji. Omówiliśmy sposób działania obu mechanizmów, funkcje PHP służące do ich obsługi (setcookie(), $_COOKIE, session_start(), $_SESSION, session_destroy() itp.), a także kluczowe aspekty konfiguracji i bezpieczeństwa. Zrozumienie różnic między ciasteczkami a sesjami oraz umiejętność ich właściwego stosowania jest fundamentalne dla tworzenia dynamicznych i bezpiecznych aplikacji internetowych.

Pamiętaj, że bezpieczeństwo danych użytkownika i jego sesji powinno być zawsze priorytetem. W następnej lekcji zajmiemy się pracą z datą i czasem w PHP.


Zadanie praktyczne

Stwórz prosty system logowania i wylogowywania użytkownika oparty na sesjach.

  1. Utwórz plik login_form.html z formularzem zawierającym pola: "Login" (username) i "Hasło" (password). Formularz powinien wysyłać dane metodą POST do skryptu login_handler.php.
  2. W skrypcie login_handler.php:
    • Rozpocznij sesję (session_start()).
    • Odbierz dane z formularza.
    • Dla uproszczenia, załóżmy, że poprawny login to "admin", a hasło to "secret123". W rzeczywistej aplikacji hasła byłyby hashowane i sprawdzane z bazą danych.
    • Jeśli dane logowania są poprawne: zregeneruj ID sesji (session_regenerate_id(true)), zapisz w $_SESSION informację o zalogowanym użytkowniku (np. $_SESSION["user"] = "admin"; $_SESSION["logged_in"] = true;) i przekieruj użytkownika na stronę panel_uzytkownika.php.
    • Jeśli dane są niepoprawne, wyświetl komunikat o błędzie i link powrotny do formularza logowania.
  3. Utwórz plik panel_uzytkownika.php:
    • Rozpocznij sesję.
    • Sprawdź, czy użytkownik jest zalogowany (czy istnieje odpowiednia zmienna w $_SESSION).
    • Jeśli jest zalogowany, wyświetl powitanie (np. "Witaj, [nazwa_uzytkownika]!") oraz link/przycisk "Wyloguj" prowadzący do logout.php.
    • Jeśli nie jest zalogowany, wyświetl komunikat "Dostęp tylko dla zalogowanych użytkowników" i przekieruj na login_form.html po kilku sekundach (lub od razu).
  4. Utwórz plik logout.php:
    • Rozpocznij sesję.
    • Usuń wszystkie dane sesji (session_unset()).
    • Zniszcz sesję (session_destroy()).
    • Usuń ciasteczko sesyjne.
    • Przekieruj użytkownika na login_form.html z komunikatem o pomyślnym wylogowaniu.
  5. Na każdej stronie (login_handler.php, panel_uzytkownika.php, logout.php) pamiętaj o wywołaniu session_start() na samym początku. Zadbaj o bezpieczeństwo ciasteczka sesyjnego (HttpOnly, Secure jeśli testujesz na HTTPS, SameSite).

Pokaż przykładowe rozwiązanie

login_form.html

<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Logowanie</title>
</head>
<body>
    <h2>Zaloguj się</h2>
    <form action="login_handler.php" method="POST">
        <p><label for="username">Login:</label> <input type="text" id="username" name="username" required></p>
        <p><label for="password">Hasło:</label> <input type="password" id="password" name="password" required></p>
        <p><input type="submit" value="Zaloguj"></p>
    </form>
    <?php if(isset($_GET["error"])) echo "<p style=\"color:red;\">Błędny login lub hasło.</p>"; ?>
    <?php if(isset($_GET["logout"])) echo "<p style=\"color:green;\">Pomyślnie wylogowano.</p>"; ?>
</body>
</html>
            

login_handler.php

<?php
ini_set("session.cookie_httponly", 1);
ini_set("session.use_strict_mode", 1);
// ini_set("session.cookie_secure", 1); // Odkomentuj, jeśli testujesz na HTTPS
ini_set("session.cookie_samesite", "Lax");

if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

$poprawny_login = "admin";
$poprawne_haslo = "secret123"; // W praktyce hashowane hasło!

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $login = isset($_POST["username"]) ? trim($_POST["username"]) : "";
    $haslo = isset($_POST["password"]) ? $_POST["password"] : "";

    // W praktyce tutaj byłoby porównanie z zahashowanym hasłem z bazy
    if ($login === $poprawny_login && $haslo === $poprawne_haslo) {
        session_regenerate_id(true); // Kluczowe dla bezpieczeństwa!
        $_SESSION["user"] = $login;
        $_SESSION["logged_in"] = true;
        header("Location: panel_uzytkownika.php");
        exit;
    } else {
        header("Location: login_form.html?error=1");
        exit;
    }
} else {
    header("Location: login_form.html");
    exit;
}
?>
            

panel_uzytkownika.php

<?php
ini_set("session.cookie_httponly", 1);
ini_set("session.use_strict_mode", 1);
// ini_set("session.cookie_secure", 1);
ini_set("session.cookie_samesite", "Lax");

if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

if (isset($_SESSION["logged_in"]) && $_SESSION["logged_in"] === true && isset($_SESSION["user"])) {
    $username = htmlspecialchars($_SESSION["user"]);
    echo "<!DOCTYPE html><html lang=\"pl\"><head><meta charset=\"UTF-8\"><title>Panel Użytkownika</title></head><body>";
    echo "<h1>Witaj w panelu, $username!</h1>";
    echo "<p>To jest Twoja prywatna strefa.</p>";
    echo "<p><a href=\"logout.php\">Wyloguj</a></p>";
    echo "</body></html>";
} else {
    // Użytkownik nie jest zalogowany
    echo "<!DOCTYPE html><html lang=\"pl\"><head><meta charset=\"UTF-8\"><title>Brak dostępu</title>";
    // echo "<meta http-equiv=\"refresh\" content=\"3;url=login_form.html\">"; // Opcjonalne przekierowanie po 3s
    echo "</head><body>";
    echo "<h1 style=\"color:red;\">Brak dostępu!</h1>";
    echo "<p>Dostęp do tej strony mają tylko zalogowani użytkownicy.</p>";
    echo "<p>Zostaniesz przekierowany na stronę logowania... lub <a href=\"login_form.html\">kliknij tutaj</a>.</p>";
    echo "</body></html>";
    // Dla natychmiastowego przekierowania:
    // header("Location: login_form.html");
    // exit;
}
?>
            

logout.php

<?php
ini_set("session.cookie_httponly", 1);
ini_set("session.use_strict_mode", 1);
// ini_set("session.cookie_secure", 1);
ini_set("session.cookie_samesite", "Lax");

if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// 1. Usuń wszystkie zmienne sesyjne
$_SESSION = array(); // lub session_unset();

// 2. Zniszcz sesję
if (session_destroy()) {
    // echo "Sesja zniszczona.<br>"; // Debug
}

// 3. Usuń ciasteczko sesyjne
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(session_name(), 
              ", 
              time() - 42000,
              $params["path"],
              $params["domain"],
              $params["secure"],
              $params["httponly"]
    );
    // echo "Ciasteczko sesyjne usunięte.<br>"; // Debug
}

// 4. Przekieruj na stronę logowania z komunikatem
header("Location: login_form.html?logout=1");
exit;
?>
            

Zadanie do samodzielnego wykonania

Rozbuduj powyższy system logowania o następujące elementy:

  1. Dodaj licznik nieudanych prób logowania. Jeśli użytkownik pomyli się 3 razy podając hasło dla danego loginu, zablokuj możliwość logowania na ten login na np. 5 minut. Informacje o liczbie prób i czasie blokady przechowuj w sesji lub w tymczasowym pliku/prostej bazie danych (dla uproszczenia możesz użyć sesji, ale pamiętaj, że sesja jest per użytkownik, więc blokada będzie działać tylko dla tej samej przeglądarki/sesji).
  2. Na stronie panel_uzytkownika.php dodaj formularz pozwalający użytkownikowi ustawić kolor tła strony (np. wybór z listy: jasny, ciemny, niebieski). Zapisz ten wybór w ciasteczku (trwałym, np. na 30 dni) i przy każdym załadowaniu strony panel_uzytkownika.php odczytuj wartość z ciasteczka i dynamicznie ustawiaj styl tła strony.


FAQ - Sesje i Ciasteczka

Czy session_start() zawsze musi być na początku skryptu?

Tak, session_start() musi być wywołane przed jakimkolwiek wysłaniem treści do przeglądarki (HTML, spacje, echo). Dzieje się tak, ponieważ inicjalizacja sesji może wymagać wysłania nagłówków HTTP (np. ciasteczka sesyjnego), a nagłówki muszą być wysłane przed treścią odpowiedzi.

Jak przechowywać obiekty w sesji?

PHP automatycznie serializuje (zamienia na string) obiekty i tablice przed zapisaniem ich do $_SESSION i deserializuje (odwzorowuje z powrotem na obiekt/tablicę) przy odczycie. Aby to działało poprawnie, definicja klasy obiektu musi być dostępna (załadowana) przed wywołaniem session_start() lub przed pierwszym dostępem do obiektu z sesji.

Co to jest Session Fixation i jak się przed nim chronić?

Session Fixation to atak, w którym atakujący ustala ID sesji ofiary przed jej zalogowaniem. Gdy ofiara się zaloguje używając tego ID, atakujący może przejąć jej sesję. Główną metodą ochrony jest regeneracja ID sesji (session_regenerate_id(true)) natychmiast po każdej zmianie poziomu uwierzytelnienia (np. po zalogowaniu).

Czy mogę używać sesji bez ciasteczek?

Tak, PHP pozwala na przekazywanie ID sesji w URL (jeśli session.use_cookies=0 i session.use_trans_sid=1), ale jest to zdecydowanie niezalecane ze względów bezpieczeństwa (ID sesji jest widoczne, może być logowane, udostępniane). Zawsze preferuj użycie ciasteczek z odpowiednimi flagami bezpieczeństwa.

Jak długo trwa sesja PHP?

Domyślnie ciasteczko sesyjne wygasa po zamknięciu przeglądarki. Jednak dane sesji na serwerze są usuwane przez mechanizm garbage collection po czasie nieaktywności określonym w session.gc_maxlifetime (domyślnie 24 minuty). Czas życia sesji można kontrolować tymi ustawieniami oraz session.cookie_lifetime.

Gdzie są przechowywane pliki sesji na serwerze?

Jeśli używany jest domyślny handler files, ścieżka do katalogu z plikami sesji jest określona w dyrektywie session.save_path w php.ini. Każda sesja to osobny plik, zwykle o nazwie sess_<ID_SESJI>.

Czy mogę przechowywać w ciasteczkach dane wrażliwe?

Zdecydowanie nie zaleca się przechowywania danych wrażliwych (haseł, danych osobowych, numerów kart) w ciasteczkach, ponieważ są one przechowywane po stronie klienta i mogą być odczytane lub zmodyfikowane. Jeśli musisz, dane powinny być silnie zaszyfrowane, ale sesje są znacznie lepszym miejscem na takie informacje.

Co oznacza atrybut SameSite dla ciasteczek?

Atrybut SameSite (Strict, Lax, None) kontroluje, czy ciasteczko jest wysyłane z żądaniami cross-site (z innej domeny). Pomaga to chronić przed atakami CSRF. Lax jest dobrym kompromisem, a Strict zapewnia najsilniejszą ochronę, ale może wpływać na niektóre funkcjonalności. None wymaga flagi Secure.