Файл авторизации на PHP: от простого к защищённому входу

Раздел: Веб-безопасность -> Аутентификация и сессии

Файл авторизации (login.php) на PHP: варианты реализации и безопасность

Как создать защищённую авторизацию с использованием современных методов PHP?

Основной эффективный вариант строится на подготовленных запросах PDO, функциях password_hash() и password_verify(), а также управлении сессиями с регенерацией идентификатора. Такой подход защищает от SQL-инъекций, атаки по словарю и фиксации сессии.


<?php
session_start();
require_once 'db.php'; // подключение к БД через PDO

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
    $login = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';

    if (empty($login) || empty($password)) {
        $_SESSION['error'] = 'Заполните все поля';
        header('Location: login.php');
        exit;
    }

    // Подготовленный запрос
    $stmt = $pdo->prepare('SELECT id, username, password FROM users WHERE username = :username');
    $stmt->execute(['username' => $login]);
    $user = $stmt->fetch();

    if ($user && password_verify($password, $user['password'])) {
        session_regenerate_id(true); // защита от фиксации сессии
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['username'] = $user['username'];
        unset($_SESSION['error']);
        header('Location: dashboard.php');
        exit;
    } else {
        $_SESSION['error'] = 'Неверное имя пользователя или пароль';
        header('Location: login.php');
        exit;
    }
}
?>
  

Php сессии файл (сохранение сессий в файлы в php)

Типичные ошибки:

  • Не вызывается session_regenerate_id() - уязвимость к фиксации сессии.
  • Хранение паролей в открытом виде или в MD5 - легко взламывается.
  • Отсутствие проверки метода запроса (GET вместо POST) - возможна утечка данных через историю браузера.
  • Необработанные исключения PDO - могут раскрыть структуру БД.

Решение: использовать try-catch для запросов к БД, задать режим ошибок PDO::ERRMODE_EXCEPTION, а в продакшене логировать исключения без вывода пользователю.

Цель: обеспечить базовую, но надёжную аутентификацию для небольших проектов, где не требуется сложная система прав или двухфакторка.

Как реализовать авторизацию без использования современных хешей (устаревший подход)?

Раньше пароли часто хешировали через md5() или sha1() без соли. Этот вариант крайне небезопасен, но его можно встретить в старых проектах.


$password = md5($_POST['password']);
$query = "SELECT id FROM users WHERE username='$login' AND password='$password'";
$result = mysql_query($query); // mysql_* устарел!
  

Php авторизация файл (файл авторизации (login.php) на php)

Проблемы: SQL-инъекции при конкатенации строк, радужные таблицы для MD5, отсутствие соли, устаревшие функции mysql_* (удалены в PHP 7).

Решение (если проект уже на таком коде): срочно мигрировать на PDO и password_hash().

Цель: понимание, почему такой метод опасен, и как от него отказываться.

Как реализовать вход с запоминанием пользователя (Remember Me)?

Для функции «Запомнить меня» создаётся токен, хранящийся в куках и в БД. Это удобно, но требует осторожности.


if ($_POST['remember']) {
    $token = bin2hex(random_bytes(32));
    $stmt = $pdo->prepare('UPDATE users SET remember_token = :token WHERE id = :id');
    $stmt->execute(['token' => password_hash($token, PASSWORD_DEFAULT), 'id' => $user['id']]);
    setcookie('remember', $token, time() + 86400 * 30, '/', '', true, true);
}
  

Ошибка: хранение токена в куках без подписи или в незашифрованном виде. Злоумышленник, получив куки, может войти. Решение: хранить только хеш токена в БД, а в куки - сам токен; при проверке сравнивать через password_verify().

Цель: повышение удобства для пользователя при сохранении приемлемого уровня безопасности.

Как добавить двухфакторную аутентификацию (2FA) в login.php?

После проверки пароля пользователь должен ввести одноразовый код, например, из Google Authenticator. Используется библиотека PHPGangsta/GoogleAuthenticator.


// После успешной проверки пароля
$_SESSION['pending_2fa'] = $user['id'];
header('Location: verify_2fa.php');
exit;
  

Проблема: неправильная синхронизация времени на сервере - коды не совпадают. Решение: настроить NTP и проверять временной дрифт.

Цель: значительно повысить защиту учётной записи, особенно в административных панелях.

Расширенные примеры и детальные пояснения

Ниже приведён полный, готовый к использованию файл login.php с использованием PDO, password_hash, CSRF-защиты и регенерации сессии. Код включает обработку ошибок и корректные редиректы.

Пример

<?php
// login.php
session_start();

// Настройка БД (db.php)
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
$user = 'root';
$password = '';
$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];
try {
    $pdo = new PDO($dsn, $user, $password, $options);
} catch (PDOException $e) {
    error_log('DB connection failed: ' . $e->getMessage());
    die('Ошибка подключения к базе данных');
}

// Генерация CSRF-токена
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Обработка POST-запроса
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'login') {
    // Проверка CSRF
    if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
        die('Недействительный CSRF-токен');
    }

    $username = trim($_POST['username'] ?? '');
    $password_input = $_POST['password'] ?? '';
    $remember = isset($_POST['remember']);

    if (empty($username) || empty($password_input)) {
        $_SESSION['error'] = 'Имя пользователя и пароль обязательны';
        header('Location: login.php');
        exit;
    }

    // Подготовленный запрос
    $sql = 'SELECT id, username, password, email, role FROM users WHERE username = :username LIMIT 1';
    $stmt = $pdo->prepare($sql);
    $stmt->execute(['username' => $username]);
    $user = $stmt->fetch();

    if (!$user || !password_verify($password_input, $user['password'])) {
        $_SESSION['error'] = 'Неверное имя пользователя или пароль';
        header('Location: login.php');
        exit;
    }

    // Регенерация сессии
    session_regenerate_id(true);
    $_SESSION['user_id'] = $user['id'];
    $_SESSION['username'] = $user['username'];
    $_SESSION['email'] = $user['email'];
    $_SESSION['role'] = $user['role'];

    // Обработка Remember Me
    if ($remember) {
        $token = bin2hex(random_bytes(32));
        $hashed_token = password_hash($token, PASSWORD_DEFAULT);
        $stmt = $pdo->prepare('UPDATE users SET remember_token = :token WHERE id = :id');
        $stmt->execute(['token' => $hashed_token, 'id' => $user['id']]);
        setcookie('remember', $token, time() + 86400 * 30, '/', '', true, true);
    } else {
        // Удаление старого токена, если был
        $stmt = $pdo->prepare('UPDATE users SET remember_token = NULL WHERE id = :id');
        $stmt->execute(['id' => $user['id']]);
        setcookie('remember', '', time() - 3600, '/', '', true, true);
    }

    unset($_SESSION['error'], $_SESSION['csrf_token']);
    header('Location: dashboard.php');
    exit;
}

// HTML-форма (упрощённо)
?>
<!DOCTYPE html>
<html>
<head><title>Вход</title></head>
<body>
    <?php if (isset($_SESSION['error'])): ?>
        <p class="error"><?= htmlspecialchars($_SESSION['error']) ?></p>
    <?php endif; ?>
    <form method="post" action="">
        <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
        <input type="hidden" name="action" value="login">
        <label>Имя: <input type="text" name="username" required></label><br>
        <label>Пароль: <input type="password" name="password" required></label><br>
        <label><input type="checkbox" name="remember"> Запомнить меня</label><br>
        <button type="submit">Войти</button>
    </form>
</body>
</html>

Результат выполнения: при корректных данных пользователь перенаправляется на dashboard.php, в сессии сохраняются id, имя, email, роль. При ошибке - возвращается на страницу логина с сообщением. Кука remember устанавливается на 30 дней, только через HTTPS (флаг secure).

HTTP/1.1 302 Found
Location: dashboard.php
Content-Type: text/html; charset=UTF-8
Set-Cookie: remember=abc123...; Path=/; Secure; HttpOnly; SameSite=Lax

(Страница dashboard.php)

Дополнительный пример: использование password_hash при регистрации. В этом случае пароль хранится безопасно.

Пример

// register.php (фрагмент)
$password_hash = password_hash($_POST['password'], PASSWORD_BCRYPT, ['cost' => 12]);
$stmt = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');
$stmt->execute(['username' => $_POST['username'], 'password' => $password_hash]);
echo 'Пользователь зарегистрирован. ID: ' . $pdo->lastInsertId();
Пользователь зарегистрирован. ID: 42
(В базе данных пароль хранится как $2y$12$...) 

Пример ошибки: неправильное использование password_verify - если передать не хеш, а сам пароль. В результате функция всегда возвращает false. Правильно: хранить только хеш, при проверке сравнивать с введённым паролем.

Пример

// Ошибочный код:
if (password_verify($user['password'], $password_input)) { ... }
// Поменяны местами аргументы! Правильно: password_verify($password_input, $user['password'])
Всегда false, даже при верном пароле.

Также стоит упомянуть rate limiting - ограничение количества попыток входа. Это предотвращает брутфорс. Пример (с использованием memcached или файла):

Пример

$ip = $_SERVER['REMOTE_ADDR'];
$key = 'login_attempts_' . $ip;
$attempts = apcu_fetch($key) ?: 0;
if ($attempts >= 5) {
    die('Слишком много попыток. Попробуйте через 5 минут.');
}
// ... после неудачной попытки
apcu_store($key, $attempts + 1, 300); // 5 минут
При превышении лимита - сообщение об ошибке.

Файл авторизации (login.php) на PHP - comments

En
Php авторизация файл (php)