Сессии, cookies и безопасный вход в PHP

PHP / Безопасность

Сессии, cookies и безопасный вход в PHP

Как настроить cookie сессии, проверить пароль, обновить хеш, сменить session ID, защитить POST через CSRF и корректно выйти.

Опубликовано 04.09.2026 6 минут чтения
Содержание статьи

Cookie хранится в браузере и отправляется серверу с подходящими запросами. PHP-сессия обычно хранит данные на сервере, а браузер получает только случайный идентификатор в cookie. Аутентификация отвечает на вопрос «кто пользователь», авторизация — «что ему разрешено».

Безопасный вход состоит не только из сравнения пароля: нужны HTTPS, защищённая cookie, смена идентификатора сессии, CSRF, ограничение попыток и проверка прав на каждом действии.

Настройка сессии

Параметры cookie задаются до session_start() и любого вывода:

PHP
<?php​declare(strict_types=1);​ini_set('session.use_strict_mode', '1');ini_set('session.use_only_cookies', '1');​session_name('app_session');session_set_cookie_params([    'lifetime' => 0,    'path' => '/',    'domain' => '',    'secure' => true,    'httponly' => true,    'samesite' => 'Lax',]);​session_start();​

  • Secure отправляет cookie только по HTTPS;
  • HttpOnly запрещает обычный доступ из JavaScript и уменьшает ущерб от части XSS;
  • SameSite=Lax ограничивает отправку в межсайтовых сценариях, но не заменяет CSRF-токен;
  • use_strict_mode запрещает принимать неизвестный идентификатор сессии.

Локальный HTTP требует отдельной development-конфигурации с secure => false. В production отключать Secure ради удобства нельзя.

Не запускайте сессию на каждой публичной странице без необходимости: cookie и Cache-Control могут ухудшить кеширование. Открывайте её там, где нужно серверное состояние.

Хранение пароля

При регистрации:

PHP
$passwordHash = password_hash($password, PASSWORD_DEFAULT);​

Не задавайте соль вручную: password_hash() создаёт безопасную случайную соль и включает параметры в результат. Столбец базы должен вмещать будущие форматы; VARCHAR(255) — практичный размер.

Перед хешированием проверьте разумную длину пароля в байтах и правила продукта. Не используйте SHA-256, MD5 или самодельное сочетание хешей для хранения паролей.

Получение данных входа

PHP
$email = is_string($_POST['email'] ?? null)    ? trim($_POST['email'])    : '';$password = is_string($_POST['password'] ?? null)    ? $_POST['password']    : '';​if (filter_var($email, FILTER_VALIDATE_EMAIL) === false || $password === '') {    $loginError = 'Неверный email или пароль.';}​

Сообщение одинаково для неизвестного email и неверного пароля, чтобы не раскрывать наличие учётной записи.

До обработки POST проверьте CSRF-токен. Дополнительно ограничьте частоту попыток по учётной записи и другим надёжным сигналам; простая блокировка только по IP может затронуть общую сеть.

Поиск пользователя и проверка

PHP
$statement = $database->prepare(    'SELECT id, password_hash, is_active     FROM users     WHERE email_normalized = :email     LIMIT 1');$statement->execute(['email' => mb_strtolower($email)]);​$user = $statement->fetch();​$dummyHash = DUMMY_PASSWORD_HASH;$storedHash = is_array($user) && is_string($user['password_hash'] ?? null)    ? $user['password_hash']    : $dummyHash;​$passwordIsValid = password_verify($password, $storedHash);$userIsActive = is_array($user) && (int) ($user['is_active'] ?? 0) === 1;​if (!$passwordIsValid || !$userIsActive) {    $loginError = 'Неверный email или пароль.';}​

DUMMY_PASSWORD_HASH — валидный хеш фиктивного пароля, созданный при настройке приложения и сохранённый в конфигурации. Он заставляет выполнить дорогую проверку даже для неизвестного email и уменьшает заметную разницу во времени ответа.

Нормализация email зависит от требований продукта. Домен регистронезависим, а локальную часть нельзя бездумно преобразовывать во всех системах; выбранное правило должно совпадать при регистрации, входе и уникальном индексе.

Успешный вход

PHP
if (!isset($loginError) && is_array($user)) {    session_regenerate_id(true);​    $_SESSION['user_id'] = (int) $user['id'];    $_SESSION['authenticated_at'] = time();    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));​    if (password_needs_rehash($storedHash, PASSWORD_DEFAULT)) {        $newHash = password_hash($password, PASSWORD_DEFAULT);​        $update = $database->prepare(            'UPDATE users SET password_hash = :password_hash WHERE id = :id'        );        $update->execute([            'password_hash' => $newHash,            'id' => (int) $user['id'],        ]);    }​    header('Location: /account', true, 303);    exit;}​

session_regenerate_id(true) предотвращает сохранение заранее известного идентификатора после повышения привилегий. CSRF-токен также обновляется.

password_needs_rehash() позволяет перейти на новые параметры при следующем успешном входе.

Проверка пользователя и прав

PHP
function requireUserId(): int{    $userId = $_SESSION['user_id'] ?? null;​    if (!is_int($userId) || $userId < 1) {        header('Location: /login', true, 303);        exit;    }​    return $userId;}​

Эта функция подтверждает только наличие ID в сессии. Перед редактированием статьи сервер проверяет, активен ли пользователь и имеет ли нужную роль или право на конкретный объект.

Нельзя доверять скрытому user_id, роли из формы или только отсутствию кнопки в интерфейсе.

CSRF для изменяющих действий

PHP
function requireValidCsrfToken(): void{    $sessionToken = $_SESSION['csrf_token'] ?? null;    $requestToken = $_POST['csrf_token'] ?? null;​    if (        !is_string($sessionToken)        || !is_string($requestToken)        || !hash_equals($sessionToken, $requestToken)    ) {        http_response_code(403);        exit('Не удалось проверить запрос.');    }}​

Вызывайте проверку до POST, PUT, PATCH и DELETE-действия. SameSite помогает, но не является единственной защитой.

Выход

Выход выполняется POST-запросом с CSRF, а не ссылкой GET:

PHP
requireValidCsrfToken();​$_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();​header('Location: /login', true, 303);exit;​

Параметры удаления должны совпадать с исходной cookie.

Обычная cookie без сессии

Для несекретного предпочтения:

PHP
setcookie('theme', 'dark', [    'expires' => time() + 60 * 60 * 24 * 365,    'path' => '/',    'secure' => true,    'httponly' => true,    'samesite' => 'Lax',]);​

Cookie полностью контролируется клиентом. Не храните в ней роль, цену или право доступа без серверной проверки и подходящей криптографической схемы. Даже подписанная cookie может быть украдена, поэтому минимизируйте срок и объём.

Дополнительные меры

  • rate limiting и журнал подозрительных входов;
  • MFA для чувствительных ролей;
  • повторная проверка пароля перед критичным действием;
  • срок бездействия и абсолютный срок сессии;
  • периодическая смена ID для долгих сессий без потери данных;
  • CSP и экранирование против XSS;
  • нейтральные публичные ошибки;
  • отзыв активных сессий после смены пароля;
  • проверка Host и redirect URL по allowlist.

Что запомнить

Хешируйте пароль через password_hash(), проверяйте через password_verify() и обновляйте через password_needs_rehash(). После входа меняйте session ID, защищайте все изменяющие запросы CSRF-токеном и проверяйте права на сервере. Cookie сессии требует HTTPS, Secure, HttpOnly и обоснованного SameSite.

Сообщить об ошибке в статье

Рекомендации редакции

Другие публикации из этого раздела.

Новые материалы

Получайте практические разборы без информационного шума

Одна общая рассылка о новых статьях и инструментах. Адрес будет активироваться только после подтверждения.

Подписка появится после подключения сервиса рассылок и Yandex SmartCaptcha.