Введение в OAuth 2.0 и JWT на PHP
Современные веб-приложения всё чаще отходят от классической сессионной аутентификации в пользу более гибких и масштабируемых решений. Два ключевых протокола — OAuth 2.0 и JWT (JSON Web Tokens) — стали стандартом де-факто для авторизации в REST API, мобильных приложениях и микросервисной архитектуре. В этой статье мы на практических примерах разберём, как реализовать OAuth 2.0 и JWT авторизацию на PHP, используя современные библиотеки и лучшие практики безопасности.
Вы узнаете, как настроить сервер авторизации, выпускать и проверять JWT токены, организовать выдачу refresh-токенов и защитить свои API-эндпоинты. Все примеры кода готовы к использованию в ваших проектах.
Основы протокола OAuth 2.0
OAuth 2.0 — это протокол авторизации, который позволяет приложениям получать ограниченный доступ к ресурсам пользователя без передачи его учётных данных. В контексте PHP-приложения OAuth 2.0 обычно реализуется через грант авторизационного кода (Authorization Code Grant) или грант пароля (Password Grant) для собственных сервисов.
Ключевые роли в OAuth 2.0
- Resource Owner — пользователь, который владеет данными
- Client — приложение, запрашивающее доступ (например, ваш PHP-фронтенд)
- Authorization Server — сервер, выдающий токены
- Resource Server — сервер, предоставляющий защищённые ресурсы (ваш API)
Поток получения токена через Authorization Code Grant
- Пользователь переходит на страницу авторизации
- После входа пользователь перенаправляется обратно в приложение с временным кодом
- PHP-сервер обменивает код на access_token и refresh_token
- Все последующие запросы к API выполняются с заголовком
Authorization: Bearer <access_token>
Реализация JWT на PHP с библиотекой firebase/php-jwt
Для работы с JWT в PHP мы будем использовать популярную библиотеку firebase/php-jwt. Установим её через Composer:
composer require firebase/php-jwt
Генерация JWT токена
Создадим функцию для выпуска access-токена с полезной нагрузкой (claims):
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
function generateAccessToken(int $userId, string $email): string {
$secretKey = 'your-256-bit-secret-key-here';
$issuedAt = time();
$expirationTime = $issuedAt + 3600; // 1 час
$payload = [
'iss' => 'pythonlib.ru',
'iat' => $issuedAt,
'exp' => $expirationTime,
'sub' => $userId,
'email' => $email,
'roles' => ['user']
];
return JWT::encode($payload, $secretKey, 'HS256');
}
Верификация JWT токена
Проверка подлинности токена при каждом запросе к защищённому ресурсу:
<?php
function validateAccessToken(string $token): ?object {
$secretKey = 'your-256-bit-secret-key-here';
try {
$decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
return $decoded;
} catch (\Exception $e) {
// Токен истёк, недействителен или подпись не совпадает
return null;
}
}
Middleware для проверки JWT
Пример простого middleware, который можно использовать с любым фреймворком:
<?php
function authMiddleware(): ?object {
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';
if (!preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
http_response_code(401);
echo json_encode(['error' => 'Токен не предоставлен']);
exit;
}
$token = $matches[1];
$user = validateAccessToken($token);
if (!$user) {
http_response_code(401);
echo json_encode(['error' => 'Токен недействителен или истёк']);
exit;
}
return $user;
}
// Использование в эндпоинте:
// $currentUser = authMiddleware();
// Теперь $currentUser содержит данные пользователя из токена
Реализация полного OAuth 2.0 сервера на PHP
Теперь объединим OAuth 2.0 и JWT в полноценный сервер авторизации. Мы реализуем грант пароля (password grant), который подходит для доверенных клиентов (например, ваше мобильное приложение или SPA).
Эндпоинт для получения токена
<?php
// /token.php
require_once 'vendor/autoload.php';
function handleTokenRequest(): void {
$grantType = $_POST['grant_type'] ?? '';
$clientId = $_POST['client_id'] ?? '';
$clientSecret = $_POST['client_secret'] ?? '';
// Проверка клиента (в реальном проекте — из БД)
if ($clientId !== 'my_app' || $clientSecret !== 'my_secret') {
http_response_code(401);
echo json_encode(['error' => 'invalid_client']);
return;
}
if ($grantType === 'password') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
// Проверка учётных данных (в реальном проекте — через password_verify)
if ($username === 'user@example.com' && $password === 'password123') {
$userId = 1;
$accessToken = generateAccessToken($userId, $username);
$refreshToken = bin2hex(random_bytes(32)); // Простой refresh токен
// Сохраняем refresh токен в БД
// saveRefreshToken($userId, $refreshToken);
echo json_encode([
'access_token' => $accessToken,
'token_type' => 'Bearer',
'expires_in' => 3600,
'refresh_token' => $refreshToken
]);
} else {
http_response_code(401);
echo json_encode(['error' => 'invalid_grant']);
}
}
}
handleTokenRequest();
Обновление access-токена через refresh_token
<?php
// /refresh.php
function handleRefreshRequest(): void {
$refreshToken = $_POST['refresh_token'] ?? '';
// Проверяем refresh токен в БД
// $storedToken = getRefreshTokenFromDB($refresh