Полное руководство по системе логина и работе с пользователями

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

Основные подходы к аутентификации и управлению пользователями

Эффективное решение на основе сессий и prepared statements

Наиболее распространённый и безопасный способ авторизации в классических веб-приложениях - использование серверных сессий. Пароли хранятся в хэшированном виде (password_hash), а идентификатор пользователя сохраняется в сессии после успешного входа. Для управления пользователями создаётся таблица users с полями id, email, password_hash, role, и т.д.

Пример минимального файла index.php?com=login:


// index.php
$action = $_GET['com'] ?? '';
if ($action === 'login') {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $email = $_POST['email'];
        $password = $_POST['password'];
        $db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
        $stmt = $db->prepare('SELECT id, password_hash FROM users WHERE email = ?');
        $stmt->execute([$email]);
        $user = $stmt->fetch();
        if ($user && password_verify($password, $user['password_hash'])) {
            session_start();
            $_SESSION['user_id'] = $user['id'];
            session_regenerate_id();
            header('Location: dashboard.php');
            exit;
        }
        $error = 'Неверные учетные данные';
    }
    // форма логина
}

После входа на защищённых страницах проверяется наличие $_SESSION['user_id']. Для выхода вызывается session_destroy().

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

  • Использование sha1 или md5 вместо password_hash - пароли легко восстанавливаются.
  • Отсутствие session_regenerate_id() - уязвимость к фиксации сессии.
  • Прямая конкатенация в SQL - инъекции. Решение: только prepared statements.

Как организовать «Запомнить меня» через куки?

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


// при успешном логине
if ($remember) {
    $token = bin2hex(random_bytes(32));
    $stmt = $db->prepare('INSERT INTO auth_tokens (user_id, token) VALUES (?, ?)');
    $stmt->execute([$user['id'], hash('sha256', $token)]);
    setcookie('remember', $token, time() + 86400 * 30, '/', '', true, true);
}

Ошибка: хранение токена в куке без хэша - компрометация куки даёт доступ. Решение: хранить хэш в базе.

Как реализовать JWT-авторизацию для API?

JWT (JSON Web Token) позволяет избежать хранения сессии на сервере. Токен подписывается секретом и содержит данные пользователя. Подходит для REST API и микросервисов.


use Firebase\JWT\JWT;
$key = 'secret';
$payload = [
    'user_id' => 123,
    'role' => 'admin',
    'exp' => time() + 3600
];
$jwt = JWT::encode($payload, $key, 'HS256');
// отправляем клиенту

Проверка: декодируем JWT, извлекаем user_id, доступ к данным через middleware.

Ошибка: использование слишком короткого или публичного секрета. Решение: генерировать сложный ключ, хранить вне кода.

Как добавить вход через социальные сети (OAuth2)?

OAuth2 делегирует аутентификацию провайдеру (Google, GitHub). Пользователь перенаправляется на страницу провайдера, после подтверждения получает код авторизации, обменивает его на токен доступа и затем получает профиль.


// пример с библиотекой league/oauth2-client
$provider = new Google(['clientId' => '...', 'clientSecret' => '...', 'redirectUri' => '...']);
if (!isset($_GET['code'])) {
    $authUrl = $provider->getAuthorizationUrl();
    header('Location: '.$authUrl);
} else {
    $token = $provider->getAccessToken('authorization_code', ['code' => $_GET['code']]);
    $user = $provider->getResourceOwner($token);
    // логин/регистрация по email
}

Ошибка: незащищённый redirect_uri - редирект на опасный сайт. Решение: проверять совпадение.

Как настроить базовую HTTP-аутентификацию?

Простой метод для защищённых областей: клиент отправляет логин и пароль в заголовке Authorization: Basic .... Используется редко из-за отсутствия гибкости, но подходит для API.


if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="My Site"');
    header('HTTP/1.0 401 Unauthorized');
    exit;
} else {
    $user = $_SERVER['PHP_AUTH_USER'];
    $pass = $_SERVER['PHP_AUTH_PW'];
    // проверка
}

Ошибка: передача данных в открытом виде без HTTPS. Решение: обязательное использование HTTPS.

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

После успешного ввода пароля запрашивается одноразовый код из TOTP-приложения (Google Authenticator) или по SMS.


// генерация секрета
$secret = Google2FA::generateSecretKey();
// сохранение в БД
// при входе - проверка
$valid = Google2FA::verifyKey($secret, $_POST['otp']);

Ошибка: синхронизация времени. Решение: использовать библиотеки с учётом допустимой задержки.

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

Регистрация с подтверждением email

После регистрации генерируется токен, отправляется письмо. При переходе по ссылке происходит активация.

Пример

// регистрация
$token = bin2hex(random_bytes(32));
$stmt = $db->prepare('INSERT INTO users (email, password_hash, confirmation_token) VALUES (?, ?, ?)');
$stmt->execute([$email, password_hash($pass, PASSWORD_DEFAULT), $token]);
$link = "https://example.com/confirm?token=$token";
// отправка письма (mail() или PHPMailer)
Результат: в почтовом ящике письмо со ссылкой вида https://example.com/confirm?token=3f8a2b...

Восстановление пароля с временным токеном

Алгоритм: запрос email -> создание токена с временем жизни -> отправка -> проверка -> смена пароля.

Пример

// запрос
$token = bin2hex(random_bytes(16));
$exp = date('Y-m-d H:i:s', strtotime('+1 hour'));
$stmt = $db->prepare('INSERT INTO password_resets (email, token, expires_at) VALUES (?, ?, ?)');
$stmt->execute([$email, hash('sha256', $token), $exp]);
// ссылка: https://example.com/reset?token=$token
Результат: письмо с одноразовой ссылкой, действительной 1 час.

Лимитирование попыток входа (rate limiting)

Для предотвращения брутфорса записываются IP и время попыток. При превышении лимита - временная блокировка.

Пример

$ip = $_SERVER['REMOTE_ADDR'];
$stmt = $db->prepare('SELECT COUNT(*) FROM login_attempts WHERE ip = ? AND attempted_at > NOW() - INTERVAL 15 MINUTE');
$stmt->execute([$ip]);
if ($stmt->fetchColumn() >= 5) {
    exit('Слишком много попыток. Повторите через 15 минут.');
}
// после неудачной попытки
$stmt = $db->prepare('INSERT INTO login_attempts (ip, attempted_at) VALUES (?, NOW())');
$stmt->execute([$ip]);
Результат: после 5 неудачных попыток с одного IP в течение 15 минут вход блокируется.

Использование сессий в Redis для высокой нагрузки

Вместо файлов сессии можно хранить в Redis, что ускоряет работу и даёт возможность масштабирования.

Пример

// установка в php.ini или в коде
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379');
// работа с сессиями остаётся такой же
session_start();
$_SESSION['user_id'] = 123;
Результат: данные сессии хранятся в Redis, что повышает производительность на кластерных конфигурациях.

Middleware для проверки ролей (на примере Laravel-подобного роутинга)

Собственная реализация: массив маршрутов с required_role и функция проверки.

Пример

function middleware($requiredRole) {
    session_start();
    if (!isset($_SESSION['role']) || $_SESSION['role'] !== $requiredRole) {
        header('HTTP/1.0 403 Forbidden');
        exit('Доступ запрещён');
    }
}
// в начале скрипта
if ($_GET['action'] === 'admin') {
    middleware('admin');
    // код для администратора
}
Результат: пользователь без роли admin получает 403 ошибку.
- Php admin authorize (авторизация администратора в php)
- Index php com login (авторизация и управление пользователями)

Авторизация и управление пользователями - comments

En
Index php com login (php)