Сценарии проверки личности в веб-безопасности PHP

Раздел: Веб-безопасность -> Безопасность

Аутентификация в PHP: подходы и реализация

Современная сессионная аутентификация с хешированием паролей (bcrypt)

Этот метод считается наиболее безопасным для веб-приложений, где требуется управление сессиями на сервере. Пароли хранятся в виде хеша с солью (алгоритм bcrypt). Сессия создается после успешной проверки логина и пароля, а идентификатор сессии хранится в cookie с флагами HttpOnly, Secure, SameSite.

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

Регистрация: при получении пароля используем password_hash($password, PASSWORD_BCRYPT). Храним в БД логин и хеш. Вход: извлекаем хеш по логину, проверяем password_verify($password, $hash). При успехе - регенерируем ID сессии (session_regenerate_id(true)) и сохраняем данные пользователя в $_SESSION.


// registration.php
$password = $_POST['password'];
$hash = password_hash($password, PASSWORD_BCRYPT);
// сохраняем $hash в БД вместе с логином

// login.php
$user = getUserByLogin($_POST['login']);
if ($user && password_verify($_POST['password'], $user['hash'])) {
    session_start();
    session_regenerate_id(true);
    $_SESSION['user_id'] = $user['id'];
    $_SESSION['role'] = $user['role'];
    // редирект
}
  

Access php (доступ к файлам в php)

Типичные ошибки: не использовать password_hash с устаревшими алгоритмами (MD5, SHA1); не проверять длину и сложность пароля; не обновлять сессионный ID после входа; не устанавливать параметры cookie (HttpOnly, Secure). Решение: всегда использовать PASSWORD_BCRYPT или PASSWORD_ARGON2I; проверять пароль на минимальную длину; вызывать session_regenerate_id() при каждом успешном входе; настраивать cookie через session_set_cookie_params(...).

Этот подход подходит для большинства веб-сайтов, где требуется постоянная аутентификация пользователей.

Как организовать аутентификацию без сессий, используя только JWT (JSON Web Token)?

JWT позволяет хранить утверждения (claims) в самом токене, подписанном секретным ключом. Сервер не хранит состояние сессии. Токен передается в HTTP-заголовке Authorization: Bearer. Подходит для API и микросервисов. Реализация: при входе создаем JWT с payload (user_id, role, exp), подписываем с помощью HMAC-SHA256 (или RS256). Клиент хранит токен (в localStorage, но безопаснее в HttpOnly cookie). Каждый запрос проверяет подпись и срок действия.


use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$key = 'secret';
$payload = [
    'iss' => 'your-site',
    'iat' => time(),
    'exp' => time() + 3600,
    'user_id' => $user['id'],
    'role' => $user['role']
];
$jwt = JWT::encode($payload, $key, 'HS256');
// отправляем клиенту

// проверка
try {
    $decoded = JWT::decode($jwt, new Key($key, 'HS256'));
    // доступ к $decoded->user_id
} catch (\Exception $e) {
    // ошибка валидации
}
  

Php filter (фильтрация данных в php)

Проблемы: токен не может быть отозван до истечения срока (если не использовать черный список); утечка токена дает доступ злоумышленнику; сложность с обновлением токена (refresh token). Решение: использовать короткоживущие access token (15–30 мин) и долгоживущие refresh token с хранением в БД; хранить refresh token в HttpOnly cookie.

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

Самый простой способ для административных разделов или API. Клиент отправляет заголовок Authorization: Basic base64(login:password). Сервер проверяет. Не рекомендуется для пользовательских сайтов из-за передачи пароля в каждом запросе (даже если через HTTPS).


if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="Restricted Area"');
    header('HTTP/1.0 401 Unauthorized');
    echo 'Access denied';
    exit;
}
$login = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
// проверка
  

Php пароль (работа с паролями в php)

Пароль передается в открытом виде (base64 - не шифрование). Только HTTPS. Нет механизма выхода (logout), кроме закрытия браузера. Решение: использовать только для внутренних инструментов, обязательно через HTTPS.

Как добавить дополнительный фактор на основе временных одноразовых паролей (Google Authenticator)?

После успешного ввода логина/пароля пользователь вводит код из приложения-аутентификатора. На сервере генерируется секретный ключ (base32), который сканируется пользователем. Проверка: вычисляем TOTP по RFC 6238.


// Генерация секрета (например, через Sonata\GoogleAuthenticator)
$g = new GoogleAuthenticator();
$secret = $g->generateSecret();
// сохранить $secret для пользователя

// Проверка кода
$isValid = $g->checkCode($secret, $_POST['code']);
  

Tokens php (токены в php)

Проблемы: синхронизация времени, потеря доступа к приложению (backup codes). Решение: предоставить резервные коды при настройке 2FA.

Как разрешить вход через Google, GitHub с помощью OAuth2?

Пользователь перенаправляется на страницу провайдера, после авторизации получает authorization code, который обменивается на access token. Затем запрашиваются данные пользователя. Упрощает регистрацию, но требует доверия внешнему сервису.


// Пример с использованием библиотеки league/oauth2-client
$provider = new \League\OAuth2\Client\Provider\Google([
    'clientId'     => '...',
    'clientSecret' => '...',
    'redirectUri'  => '...',
]);
if (!isset($_GET['code'])) {
    $authUrl = $provider->getAuthorizationUrl();
    $_SESSION['oauth2state'] = $provider->getState();
    header('Location: ' . $authUrl);
    exit;
} else {
    $token = $provider->getAccessToken('authorization_code', ['code' => $_GET['code']]);
    $user = $provider->getResourceOwner($token);
    // логин по email
}
  

Проблемы: необходимость регистрации приложения у провайдера; безопасность callback URL; возможные redirect с подделкой state. Решение: проверять state-параметр для защиты от CSRF; хранить state в сессии.

- Php пароль mysql (пароль для mysql в php)
- Domain block php (блокировка домена в php)
- Auth php (аутентификация в php)

Подробные примеры реализации

Пример 1. Сессионная аутентификация с защитой от CSRF и блокировкой после неудачных попыток

Код регистрации и входа с использованием встроенных средств PHP для CSRF токенов. Покажем листинг с обработкой попыток.

Пример

<?php
// config.php
session_start();
define('MAX_ATTEMPTS', 5);
define('BLOCK_TIME', 900); // 15 минут

// registration.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $login = trim($_POST['login']);
    $password = $_POST['password'];
    $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
    // save to DB: login, hash, created_at
    // generate CSRF token
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// login.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // check CSRF
    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
        die('CSRF token mismatch');
    }
    // check block
    $attempts = $_SESSION['attempts'] ?? 0;
    $block_time = $_SESSION['block_time'] ?? 0;
    if ($attempts >= MAX_ATTEMPTS && time() - $block_time < BLOCK_TIME) {
        die('Too many attempts. Try later.');
    }
    $user = getUserByLogin($_POST['login']);
    if ($user && password_verify($_POST['password'], $user['hash'])) {
        session_regenerate_id(true);
        $_SESSION['user_id'] = $user['id'];
        unset($_SESSION['attempts'], $_SESSION['block_time']);
        // regenerate CSRF
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        header('Location: dashboard.php');
        exit;
    } else {
        $_SESSION['attempts'] = ($_SESSION['attempts'] ?? 0) + 1;
        if ($_SESSION['attempts'] >= MAX_ATTEMPTS) {
            $_SESSION['block_time'] = time();
        }
        $error = 'Invalid credentials';
    }
}
?>
Результат: пользователь блокируется на 15 минут после 5 неудачных попыток. CSRF токен защищает форму от подделки.

Пример 2. JWT аутентификация с refresh token

Создаем два токена: access (15 мин) и refresh (7 дней). Refresh хранится в БД и в HttpOnly cookie. При истечении access, клиент отправляет refresh на специальный endpoint.

Пример

<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$accessKey = 'access_secret';
$refreshKey = 'refresh_secret';
$accessExp = time() + 900;
$refreshExp = time() + 604800;

// Создание токенов при входе
$accessPayload = ['user_id' => $user['id'], 'type' => 'access', 'exp' => $accessExp];
$accessToken = JWT::encode($accessPayload, $accessKey, 'HS256');
$refreshPayload = ['user_id' => $user['id'], 'type' => 'refresh', 'exp' => $refreshExp];
$refreshToken = JWT::encode($refreshPayload, $refreshKey, 'HS256');
// Сохраняем refresh в БД (таблица refresh_tokens: user_id, token_hash, expires_at)
$tokenHash = hash('sha256', $refreshToken);
// INSERT INTO refresh_tokens (user_id, token_hash, expires_at) VALUES ($user['id'], '$tokenHash', date('Y-m-d H:i:s', $refreshExp))
// Отправляем refresh token в HttpOnly cookie (недоступен JS)
setcookie('refresh_token', $refreshToken, $refreshExp, '/', '', true, true);
// Access token передаем клиенту в ответе JSON
echo json_encode(['access_token' => $accessToken]);

// Endpoint для обновления
// if cookie refresh_token exists
$refreshToken = $_COOKIE['refresh_token'] ?? '';
try {
    $decoded = JWT::decode($refreshToken, new Key($refreshKey, 'HS256'));
    if ($decoded->type !== 'refresh') throw new Exception;
    // Проверяем, что такой токен есть в БД и не отозван
    $stored = getRefreshTokenByHash(hash('sha256', $refreshToken));
    if (!$stored || $stored['revoked']) throw new Exception;
    // Создаем новый access token
    $newAccess = JWT::encode(['user_id' => $decoded->user_id, 'type' => 'access', 'exp' => time()+900], $accessKey, 'HS256');
    echo json_encode(['access_token' => $newAccess]);
} catch (Exception $e) {
    http_response_code(401);
    echo 'Invalid refresh token';
}
?>
Результат: клиент получает короткоживущий access token, а refresh token хранится в HttpOnly cookie. При запросе защищенного ресурса сервер проверяет access. Если истек, клиент автоматически (через interceptor) запрашивает новый access по refresh.

Пример 3. Двухфакторная аутентификация с TOTP (Google Authenticator)

Реализация с использованием библиотеки (например, Spomky-Labs/otphp).

Пример

<?php
use OTPHP\TOTP;

// Настройка 2FA (после обычного входа)
$secret = TOTP::create()->getSecret(); // base32
// Показываем QR-код: otpauth://totp/Example:user?secret=...&issuer=Example
// Сохраняем secret у пользователя

// Проверка кода
$totp = TOTP::create($secret);
$isValid = $totp->verify($_POST['code'], null, 2); // допускаем 2 шага по времени
if ($isValid) {
    // завершаем аутентификацию (например, устанавливаем сессию)
    $_SESSION['2fa_verified'] = true;
}
?>
Результат: пользователь после ввода пароля должен ввести 6-значный код, который генерируется каждые 30 секунд в приложении. Без знания секрета подобрать код практически невозможно.

аутентификация в PHP - comments

En
Auth php (php)