Аутентификация запросов к веб-сервисам на PHP

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

Основные подходы к аутентификации API в PHP

Наиболее эффективное решение для современных API - аутентификация на основе JWT (JSON Web Token). JWT - это компактный, самодостаточный токен, содержащий утверждения (claims), подписанный секретным ключом или парой ключей. Сервер не хранит сессии, что упрощает масштабирование и распределённые системы.

Реализация JWT аутентификации

Используется библиотека firebase/php-jwt. Установка через Composer:

composer require firebase/php-jwt

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

Генерация токена

Создаётся токен после успешной аутентификации (логин + пароль):

<?
use Firebase\JWT\JWT;
require 'vendor/autoload.php';

$key = 'your-secret-key-here';
$payload = [
    'iss' => 'example.com',          // издатель
    'iat' => time(),                 // время выпуска
    'exp' => time() + 3600,          // срок действия (1 час)
    'sub' => $userId,                // идентификатор пользователя
    'roles' => ['user', 'admin']     // дополнительные утверждения
];
$jwt = JWT::encode($payload, $key, 'HS256');
echo $jwt;
?>

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

Проверка токена в middleware

Клиент передаёт токен в заголовке Authorization: Bearer . Сервер проверяет подпись и срок действия:

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

$key = 'your-secret-key-here';
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';

if (!preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
    http_response_code(401);
    exit('Токен не предоставлен');
}

try {
    $decoded = JWT::decode($matches[1], new Key($key, 'HS256'));
    $userId = $decoded->sub;
    // Далее работаем с аутентифицированным пользователем
} catch (\Exception $e) {
    http_response_code(401);
    exit('Недействительный токен: ' . $e->getMessage());
}
?>

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

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

  • Истечение срока токена (exp) – клиент должен обновлять токен через refresh-механизм или заново логиниться.
  • Неверный алгоритм подписи – указывать точное значение 'HS256' или 'RS256' при декодировании.
  • Утечка секретного ключа – хранить ключ в переменных окружения, не коммитить в репозиторий.
  • Межсайтовая подделка запроса (CSRF) – при использовании JWT в куках, токен в заголовке Bearer не подвержен CSRF.

Как реализовать простую аутентификацию без сторонних библиотек?

API Ключи (Bearer Token)

Самый простой вариант – назначать каждому клиенту статический токен (API-ключ) и проверять его при каждом запросе. Токен может быть хеширован в базе данных.

// Генерация ключа
$token = bin2hex(random_bytes(32));
echo $token;

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

// Проверка
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
$stmt = $pdo->prepare('SELECT id FROM users WHERE api_key = ?');
$stmt->execute([hash('sha256', $apiKey)]);
$user = $stmt->fetch();
if (!$user) {
    http_response_code(403);
    exit('Неверный ключ');
}
?>

Https load php (загрузка через https в php)

Проблемы: статические ключи сложно отзывать, нет автоматического истечения, требуется защищённое хранение на стороне клиента.

Как защитить API при помощи стандартной HTTP Basic-аутентификации?

HTTP Basic Auth

Клиент передаёт логин и пароль в заголовке Authorization: Basic base64(login:password). Способ прост, но пароль передаётся в открытом виде (только через HTTPS).

<?
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="My API"');
    http_response_code(401);
    exit('Требуется аутентификация');
}
$login = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
// Проверка в БД
if (!verifyUser($login, $password)) {
    http_response_code(403);
    exit('Неверные учётные данные');
}
?>

права php (управление правами доступа в php)

Недостатки: каждый запрос требует проверки логина/пароля, сложность с logout, уязвимость к перехвату при неиспользовании HTTPS.

Как внедрить OAuth 2.0 для сторонних приложений?

OAuth 2.0 (например, Google/Facebook)

Позволяет делегировать аутентификацию внешним провайдерам. PHP-приложение выступает в роли ресурсного сервера. Требуется библиотека (например, league/oauth2-server или интеграция с провайдером).

// Пример с Google Client
$client = new Google\Client();
$client->setClientId(GOOGLE_CLIENT_ID);
$payload = $client->verifyIdToken($idToken);
if ($payload) {
    $userId = $payload['sub'];
    // Создаём собственную сессию или JWT
}
?>

Php пароль mysql (пароль для mysql в php)

Сложности: настройка Redirect URI, хранение refresh-токенов, защита от CSRF в flow авторизации.

Как аутентифицировать запросы с подписью (HMAC)?

HMAC подпись

Клиент создаёт подпись запроса с помощью секретного ключа и хеш-функции. Сервер проверяет подпись, используя тот же ключ. Подходит для сервер-серверных интеграций.

// Создание подписи на клиенте
$secret = 'shared-secret';
$data = 'POST:/api/data:' . json_encode($body);
$signature = hash_hmac('sha256', $data, $secret);
// Заголовок X-Signature: $signature

Domain block php (блокировка домена в php)

// Проверка на сервере
$expected = hash_hmac('sha256', 'POST:/api/data:' . file_get_contents('php://input'), $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_SIGNATURE'])) {
    http_response_code(401);
    exit('Подпись не совпадает');
}
?>

Проблемы: защита от replay-атак (требуется включение timestamp в данные), сложность отзыва ключа.

- Protect php code (защита php кода)
- Index php php id token (работа с токенами в php)
- Php action login (вход (login) через php)

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

Работа с refresh-токенами в JWT

Для продления сессии без повторного ввода пароля используют пару access/refresh. Access-токен живёт коротко (15 мин), refresh – долго (30 дней).

Пример
// Генерация пары
$accessPayload = [
    'sub' => $userId,
    'exp' => time() + 900, // 15 минут
    'type' => 'access'
];
$accessToken = JWT::encode($accessPayload, $key, 'HS256');

$refreshPayload = [
    'sub' => $userId,
    'exp' => time() + 2592000, // 30 дней
    'type' => 'refresh',
    'jti' => bin2hex(random_bytes(16)) // уникальный ID для отзыва
];
$refreshToken = JWT::encode($refreshPayload, $key, 'HS256');
// Сохраняем jti в БД для последующей проверки

Эндпоинт обновления:

Пример
// /api/refresh
$refreshToken = $_POST['refresh_token'] ?? '';
try {
    $decoded = JWT::decode($refreshToken, new Key($key, 'HS256'));
    if ($decoded->type !== 'refresh') throw new Exception('Не refresh-токен');
    // Проверяем jti в БД (не отозван)
    $userId = $decoded->sub;
    // Выдаём новый access-токен
    $newAccess = JWT::encode([
        'sub' => $userId,
        'exp' => time() + 900,
        'type' => 'access'
    ], $key, 'HS256');
    echo json_encode(['access_token' => $newAccess]);
} catch (\Exception $e) {
    http_response_code(401);
    echo json_encode(['error' => 'Refresh token invalid']);
}
?>

Аутентификация с помощью HMAC и защиты от replay

Добавим timestamp и nonce, чтобы запрос не мог быть повторён злоумышленником.

Пример
// Клиент
$secret = 'my-secret';
$timestamp = time();
$nonce = bin2hex(random_bytes(8));
$body = '{"action":"read"}';
$dataToSign = $timestamp . ':' . $nonce . ':' . $body;
$signature = hash_hmac('sha256', $dataToSign, $secret);
// Заголовки: X-Timestamp, X-Nonce, X-Signature
Пример
// Сервер
$receivedTimestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? 0;
$receivedNonce = $_SERVER['HTTP_X_NONCE'] ?? '';
$receivedSignature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');

// Проверка временной метки (разница не более 5 минут)
if (abs(time() - $receivedTimestamp) > 300) {
    http_response_code(401);
    exit('Запрос устарел');
}
// Проверка nonce (хранить в redis на 5 минут)
if (nonceIsUsed($receivedNonce)) {
    http_response_code(401);
    exit('Nonce уже использован');
}
// Проверка подписи
$expected = hash_hmac('sha256', $receivedTimestamp . ':' . $receivedNonce . ':' . $body, $secret);
if (!hash_equals($expected, $receivedSignature)) {
    http_response_code(401);
    exit('Подпись недействительна');
}
?>

Использование атрибутов PHP для middleware (PSR-7 / Slim Framework)

Пример с Slim Framework 4, где аутентификация реализована в middleware с проверкой JWT.

Пример
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\App;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$app = new App();

$jwtMiddleware = function (Request $request, $handler) use ($key) {
    $authHeader = $request->getHeaderLine('Authorization');
    if (!preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
        $response = new \Slim\Psr7\Response();
        $response->getBody()->write('Unauthorized');
        return $response->withStatus(401);
    }
    try {
        $decoded = JWT::decode($matches[1], new Key($key, 'HS256'));
        $request = $request->withAttribute('user_id', $decoded->sub);
        return $handler->handle($request);
    } catch (\Exception $e) {
        $response = new \Slim\Psr7\Response();
        $response->getBody()->write('Invalid token');
        return $response->withStatus(401);
    }
};

$app->get('/api/protected', function (Request $request, Response $response) {
    $userId = $request->getAttribute('user_id');
    $response->getBody()->write("Hello user $userId");
    return $response;
})->add($jwtMiddleware);

$app->run();

Пример с использованием API-ключей и лимитов запросов

Пример
// Генерация ключа при регистрации
$rawKey = bin2hex(random_bytes(32));
$hashedKey = password_hash($rawKey, PASSWORD_BCRYPT);
// Сохраняем $hashedKey в БД, возвращаем $rawKey клиенту

// Проверка
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
$stmt = $pdo->prepare('SELECT id, rate_limit FROM users WHERE api_key_hash = ?');
$stmt->execute([hash('sha256', $apiKey)]);
$user = $stmt->fetch();

if (!$user) {
    http_response_code(403);
    exit('Invalid API key');
}
// Проверка лимита (например, Redis)
$currentCount = $redis->incr("rate:{$user['id']}");
if ($currentCount > $user['rate_limit']) {
    http_response_code(429);
    exit('Rate limit exceeded');
}
?>

Результат выполнения (для последнего примера) – при превышении лимита сервер вернёт 429 Too Many Requests.

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

En
Api auth php (php)