0, // 0 = cookie platí do zavření prohlížeče // (trvalost zajišťuje remember me, ne session) 'path' => '/', 'httponly' => true, // JavaScript k cookie nemá přístup 'samesite' => 'Strict', // ochrana před CSRF // 'secure' => true, // odkomentuj pokud web běží na HTTPS ]); session_start(); // ------------------------------------------------------------ // VÝCHOZÍ STAV – uživatel není přihlášen // ------------------------------------------------------------ // Tyto hodnoty jsou nastaveny vždy – při každém načtení stránky. // Teprve níže se případně přepíší na true / skutečné hodnoty. $auth_prihlasen = false; $auth_uzivatel = [ 'id' => null, 'email' => null, 'admin' => false, ]; $auth_logout_html = ''; $auth_login_html = ''; // ------------------------------------------------------------ // KROK 1: Existuje platná session? // ------------------------------------------------------------ // Session je nejrychlejší způsob ověření – nevyžaduje dotaz do DB. // Session data jsou uložena na serveru, uživatel je nemůže zfalšovat. if (isset($_SESSION['uzivatel_id']) && isset($_SESSION['email'])) { // Kontrola expirace session (nečinnost) if (isset($_SESSION['posledni_aktivita']) && (time() - $_SESSION['posledni_aktivita']) > SESSION_EXPIRACE) { // Session vypršela – zrušíme ji. // DŮLEŽITÉ: Po session_destroy() není žádná aktivní session. // Krok 2 (remember me) to musí zohlednit – viz níže. $_SESSION = []; session_destroy(); } else { // Session je platná – uživatel je přihlášen $auth_prihlasen = true; $auth_uzivatel = [ 'id' => $_SESSION['uzivatel_id'], 'email' => $_SESSION['email'], // Přetypování na bool – ochrana pro případ, že by v session // byla jiná hodnota než true/false 'admin' => (bool) $_SESSION['admin'], ]; // Aktualizujeme čas poslední aktivity $_SESSION['posledni_aktivita'] = time(); } } // ------------------------------------------------------------ // KROK 2: Session neexistuje – zkusíme remember me cookie // ------------------------------------------------------------ // Cookie obsahuje pouze selector a token. Nikdy neobsahuje // heslo, ID uživatele ani příznak admin. Vše citlivé je na serveru. if (!$auth_prihlasen && isset($_COOKIE['auth_selector']) && isset($_COOKIE['auth_token'])) { $cookie_selector = $_COOKIE['auth_selector']; $cookie_token = $_COOKIE['auth_token']; // Vyhledáme záznam v DB podle selectoru (přesná shoda, rychlé) $stmt = $pdo->prepare(" SELECT t.id AS token_id, t.uzivatel_id, t.token_hash, t.expiruje, u.email, u.admin FROM `" . DB_TABULKA_TOKENY . "` t JOIN `" . DB_TABULKA_UZIVATELE . "` u ON u.id = t.uzivatel_id WHERE t.selector = :selector LIMIT 1 "); $stmt->execute([':selector' => $cookie_selector]); $zaznam = $stmt->fetch(); $cookie_ok = false; if ($zaznam) { // Zkontrolujeme, zda token ještě nevypršel if (time() < strtotime($zaznam['expiruje'])) { // Ověříme tajný token pomocí password_verify(). // Tato funkce záměrně trvá stejně dlouho bez ohledu // na výsledek – chrání před timing útoky. if (password_verify($cookie_token, $zaznam['token_hash'])) { $cookie_ok = true; } } } if ($cookie_ok) { // Cookie je platná – obnovíme session. // // OPRAVA CHYBY: Pokud v kroku 1 vypršela session a byla zničena // přes session_destroy(), není nyní žádná aktivní session. // session_regenerate_id() by selhalo s warningem "no active session", // ten warning by odeslal výstup, a tím by znemožnil setcookie() // a header() níže. // // Řešení: pokud session není aktivní, spustíme ji znovu // před voláním session_regenerate_id(). if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } // Regenerujeme session ID (ochrana před session fixation) session_regenerate_id(true); $_SESSION['uzivatel_id'] = $zaznam['uzivatel_id']; $_SESSION['email'] = $zaznam['email']; $_SESSION['admin'] = (bool) $zaznam['admin']; $_SESSION['posledni_aktivita'] = time(); // Vygenerujeme CSRF token pokud ještě neexistuje if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } $auth_prihlasen = true; $auth_uzivatel = [ 'id' => $zaznam['uzivatel_id'], 'email' => $zaznam['email'], 'admin' => (bool) $zaznam['admin'], ]; // Prodloužíme platnost tokenu v DB i v cookie $nova_expirace = time() + REMEMBER_EXPIRACE; $nova_expirace_dt = date('Y-m-d H:i:s', $nova_expirace); $stmt2 = $pdo->prepare(" UPDATE `" . DB_TABULKA_TOKENY . "` SET `expiruje` = :expiruje WHERE `id` = :id "); $stmt2->execute([ ':expiruje' => $nova_expirace_dt, ':id' => $zaznam['token_id'], ]); // Prodloužíme cookie v prohlížeči setcookie('auth_selector', $cookie_selector, [ 'expires' => $nova_expirace, 'path' => '/', 'httponly' => true, 'samesite' => 'Strict', // 'secure' => true, ]); setcookie('auth_token', $cookie_token, [ 'expires' => $nova_expirace, 'path' => '/', 'httponly' => true, 'samesite' => 'Strict', // 'secure' => true, ]); // Smažeme staré (expirované) tokeny z DB příležitostně $stmt3 = $pdo->prepare(" DELETE FROM `" . DB_TABULKA_TOKENY . "` WHERE `expiruje` < :ted "); $stmt3->execute([':ted' => date('Y-m-d H:i:s')]); } else { // Cookie je neplatná nebo expirovaná – smažeme ji if ($zaznam) { $stmt4 = $pdo->prepare(" DELETE FROM `" . DB_TABULKA_TOKENY . "` WHERE `id` = :id "); $stmt4->execute([':id' => $zaznam['token_id']]); } setcookie('auth_selector', '', [ 'expires' => time() - 3600, 'path' => '/', 'httponly' => true, 'samesite' => 'Strict', ]); setcookie('auth_token', '', [ 'expires' => time() - 3600, 'path' => '/', 'httponly' => true, 'samesite' => 'Strict', ]); } } // ------------------------------------------------------------ // KROK 3: Rozhodnutí – přesměrovat nebo pokračovat? // ------------------------------------------------------------ if (!$auth_prihlasen && VYZADOVAT_PRIHLASENI) { // Uložíme URL aktuální stránky pro přesměrování po přihlášení $aktualni_url = $_SERVER['REQUEST_URI'] ?? ''; if (!empty($aktualni_url)) { $_SESSION['redirect_po_prihlaseni'] = $aktualni_url; } header('Location: ' . AUTH_LOGIN_URL); exit; } // ------------------------------------------------------------ // CSRF TOKEN – zajistíme, že vždy existuje // ------------------------------------------------------------ if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } // ------------------------------------------------------------ // PŘIPRAVENÉ HTML PROMĚNNÉ // ------------------------------------------------------------ // -- Odhlašovací formulář ------------------------------------ // Neprázdný pouze pokud je uživatel přihlášen. // Pro admina obsahuje navíc odkaz na administrační rozhraní. // Použití na stránce: echo $auth_logout_html; if ($auth_prihlasen) { $auth_logout_html = ''; // Odkaz na admin rozhraní – pouze pro admina if ($auth_uzivatel['admin']) { $auth_logout_html .= 'Administrace | '; } // Přihlášený uživatel a tlačítko odhlášení $auth_logout_html .= htmlspecialchars($auth_uzivatel['email']) . ' | ' . '
' . '' . '' . '
'; } // -- Přihlašovací formulář ----------------------------------- // Neprázdný pouze pokud uživatel NENÍ přihlášen. // Odesílá data na login.php, které zpracuje přihlášení // a přesměruje uživatele zpět na původní stránku. // Použití na stránce: echo $auth_login_html; if (!$auth_prihlasen) { // Aktuální URL předáme login.php jako parametr, // aby nás po úspěšném přihlášení přesměroval zpět sem $aktualni_url = $_SERVER['REQUEST_URI'] ?? ''; $auth_login_html = '
' // CSRF token – ochrana před podvrženými požadavky . '' // Aktuální URL – login.php nás po přihlášení přesměruje zpět . '' . '

' . '
' . '' . '

' . '

' . '
' . '' . '

' . '

' . '' . '

' . '

' . '' . '

' . '
'; }