Создание защищённого входа в PHP приложение
Разработка файла входа на PHP
Как сделать безопасный вход пользователя через PHP с использованием базы данных и сессий?
Для аутентификации чаще всего применяется связка PHP-сессий и реляционной базы данных. Пароли хранятся в виде хешей (функция password_hash), а проверка выполняется через password_verify. Скрипт login.php обрабатывает POST-запрос, сверяет данные и при успехе создаёт сессию.
<?php
session_start();
require 'db.php'; // PDO подключение
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$login = trim($_POST['login']);
$password = $_POST['password'];
$stmt = $pdo->prepare('SELECT id, password_hash FROM users WHERE login = ?');
$stmt->execute([$login]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['login'] = $login;
header('Location: dashboard.php');
exit;
} else {
$error = 'Неверный логин или пароль.';
}
}
?>
... форма ...
Admin index php login php (страница входа администратора php)
Пояснение шагов: 1) сессия стартует в самом начале; 2) подключается конфигурация БД; 3) проверяется метод запроса; 4) данные фильтруются (PDO защищает от инъекций); 5) выполняется запрос на получение хеша пароля; 6) функция password_verify сравнивает переданный пароль с хешем; 7) при совпадении в сессию записывается идентификатор пользователя; 8) редирект на защищённую страницу.
Типичные ошибки: Headers already sent возникает, если перед session_start() или header() есть вывод. Решение: убедиться, что нет пробелов или HTML до <?. Ошибка Undefined index - если форма не отправлена или поля отсутствуют. Стоит проверять существование ключей через isset(). Кроме того, без использования HTTPS пароль передаётся в открытом виде, что небезопасно. Рекомендуется строгий HTTPS.
Вход с хранением учётных записей в файле
В некоторых случаях (например, для прототипа) можно хранить логины и хеши паролей в текстовом файле. Каждая строка содержит логин и хеш через разделитель, например, двоеточие.
$file = 'users.txt';
$lines = file($file, FILE_IGNORE_NEW_LINES);
foreach ($lines as $line) {
list($stored_login, $stored_hash) = explode(':', $line, 2);
if ($stored_login === $login) {
if (password_verify($password, $stored_hash)) { ... }
}
}
Php code login (код страницы входа php)
Проблемы: нет защиты от одновременного чтения/записи, файл может быть прочитан при утечке конфигурации, отсутствует масштабирование. Такой метод не рекомендуется для реальных проектов.
Вход через LDAP (Active Directory)
Для корпоративных сетей часто используется LDAP-аутентификация. PHP предоставляет функции ldap_bind и ldap_search.
$ldap_server = 'ldap://ad.example.com';
$ldap_dn = 'cn=' . $login . ',ou=users,dc=example,dc=com';
$ldapconn = ldap_connect($ldap_server);
if (@ldap_bind($ldapconn, $ldap_dn, $password)) {
// успешная аутентификация
$_SESSION['user'] = $login;
}
Request login php (запрос на вход php)
Ошибка: неправильный DN или недоступность LDAP-сервера. Необходимо использовать @ для подавления предупреждений и проверять ошибки через ldap_error().
Аутентификация через JWT (JSON Web Token)
Для REST API часто применяется JWT. Пользователь отправляет логин/пароль, сервер возвращает токен, который клиент хранит и передаёт в заголовке Authorization. Библиотека firebase/php-jwt упрощает работу.
use Firebase\\JWT\\JWT;
$key = 'secret_key';
$payload = [
'iat' => time(),
'exp' => time() + 3600,
'user_id' => $user['id']
];
$jwt = JWT::encode($payload, $key, 'HS256');
echo json_encode(['token' => $jwt]);
Localhost register php (регистрация на локальном сервере)
Проблемы: невозможность отозвать отдельный токен до истечения срока, необходимость защищённого хранения ключа на сервере, уязвимость при неверной настройке алгоритма.
Вход через социальные сети (OAuth 2.0, Google)
OAuth позволяет делегировать аутентификацию стороннему провайдеру. После редиректа на Google и получения кода, сервер обменивает его на токен доступа и получает информацию о пользователе.
$client = new Google\\Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
if (isset($_GET['code'])) {
$token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
$oauth2 = new Google\\Service\\Oauth2($client);
$userInfo = $oauth2->userinfo->get();
// создать сессию на основе $userInfo->email
}
регистрации php mysql (регистрация пользователей на php и mysql)
Необходимость регистрации приложения в консоли Google, обработка ошибок (отказ пользователя, неверный редирект).
HTTP Basic Authentication
Для простых защищённых зон можно использовать встроенную HTTP-аутентификацию. Сервер запрашивает логин/пароль через диалоговое окно браузера.
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="Restricted Area"');
header('HTTP/1.0 401 Unauthorized');
echo 'Отменено';
exit;
}
$user = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];
// проверка...
Недостатки: нет гибкости интерфейса, пароль передаётся в base64 (не шифрован), требуется обязательный HTTPS. Не подходит для пользовательских профилей.
Расширенные сценарии файла входа PHP
Запоминание пользователя (Remember Me)
Чтобы пользователь не вводил пароль при каждом посещении, можно установить долгоживущую куку с уникальным токеном. Токен сохраняется в базе данных и связывается с пользователем.
// При успешном входе (если отмечен checkbox)
$token = bin2hex(random_bytes(32));
setcookie('remember_token', $token, time() + 30*24*3600, '/', '', true, true);
$stmt = $pdo->prepare('INSERT INTO auth_tokens (user_id, token, expires_at) VALUES (?, ?, ?)');
$stmt->execute([$user_id, password_hash($token, PASSWORD_DEFAULT), date('Y-m-d H:i:s', time()+30*24*3600)]);
// При заходе без сессии
if (!isset($_SESSION['user_id']) && isset($_COOKIE['remember_token'])) {
$stmt = $pdo->prepare('SELECT user_id FROM auth_tokens WHERE expires_at > NOW()');
$stmt->execute();
foreach ($stmt->fetchAll() as $row) {
if (password_verify($_COOKIE['remember_token'], $row['token'])) {
$_SESSION['user_id'] = $row['user_id'];
break;
}
}
}
Результат: пользователь остаётся авторизованным до 30 дней или до сброса токена.
Проблемы: токен может быть украден при XSS, поэтому кука должна быть HttpOnly и Secure. Также нужно предусмотреть возможность отзыва всех токенов пользователя (например, при смене пароля).
Защита от CSRF в форме входа
Чтобы предотвратить межсайтовую подделку запроса, следует добавлять уникальный токен в форму и проверять его на сервере.
// Генерация токена
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// В форме
// Проверка при POST
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die('CSRF атака');
}
Результат: форма входа защищена от подделки запроса с другого сайта.
Ограничение числа попыток входа
Для защиты от подбора пароля ограничивают количество неудачных попыток с одного IP или для одного логина. Реализация через файл или базу данных.
// Использование файла лога
$attempts_file = '/tmp/login_attempts_' . md5($_SERVER['REMOTE_ADDR']);
$attempts = @file_get_contents($attempts_file) ?: 0;
if ($attempts >= 5) {
$wait = time() - filemtime($attempts_file);
if ($wait < 300) {
die('Слишком много попыток. Подождите ' . (300 - $wait) . ' секунд.');
} else {
$attempts = 0;
}
}
// После неудачной попытки
file_put_contents($attempts_file, ++$attempts);
// При успешной попытке удалить файл
unlink($attempts_file);
Результат: при 5 неудачных попытках за 5 минут ввод блокируется.
Проблемы: блокировка всех пользователей за одним NAT, необходимость чистки старых файлов. Лучше использовать БД с временными метками.
Двухфакторная аутентификация (TOTP)
Дополнительная проверка через одноразовый код из приложения-аутентификатора (Google Authenticator). Используется библиотека otphp.
require 'vendor/autoload.php';
use OTPHP\\TOTP;
// При регистрации генерируем секрет
$secret = TOTP::generate()->getSecret();
// Сохраняем секрет у пользователя
// При входе после проверки пароля запрашиваем код
$totp = TOTP::createFromSecret($user['totp_secret']);
if ($totp->verify($_POST['totp_code'])) {
// успешная 2FA
} else {
// неверный код
}
Результат: дополнительный уровень безопасности, код меняется каждые 30 секунд.