Реализация входа в веб-API на PHP

Раздел: Веб-разработка -> системы аутентификации

Создание API входа на PHP требует понимания различных подходов к аутентификации. Выбор зависит от архитектуры приложения: монолитная или микросервисная, наличие клиентских приложений (SPA, мобильные), требования к безопасности. Рассмотрим несколько вариантов, от простого сессионного механизма до токенов JWT и протокола OAuth2.

Основное решение: JWT аутентификация (stateless)

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

1. Установка библиотеки

Используем firebase/php-jwt через Composer:

composer require firebase/php-jwt

Api login php (api входа на php)

2. Генерация токена при входе

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

$secretKey = 'ваш_секретный_ключ'; // минимум 32 символа

function generateToken($userId, $username) {
    $payload = [
        'iss' => 'your-api',
        'iat' => time(),
        'exp' => time() + 3600, // 1 час
        'sub' => $userId,
        'name' => $username
    ];
    return JWT::encode($payload, $secretKey, 'HS256');
}

Эндпоинт /login принимает логин и пароль, проверяет их (например, через bcrypt) и возвращает токен.

3. Проверка токена (middleware)

function authenticateToken($headers) {
    if (!isset($headers['Authorization'])) {
        http_response_code(401);
        echo json_encode(['error' => 'Токен не предоставлен']);
        exit;
    }
    $token = str_replace('Bearer ', '', $headers['Authorization']);
    try {
        $decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
        return $decoded; // объект с sub, name и т.д.
    } catch (\Exception $e) {
        http_response_code(401);
        echo json_encode(['error' => 'Неверный токен: ' . $e->getMessage()]);
        exit;
    }
}

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

  • Токен истёк - клиент должен обновить токен через refresh token или повторный вход.
  • Секретный ключ слабый - используйте случайную строку длиной не менее 256 бит.
  • Алгоритм подписи не указан явно - в firebase/php-jwt версии 6+ обязательно передавать объект Key.

Как реализовать сессионную аутентификацию для API?

Традиционный подход с использованием нативных PHP-сессий (PHPSESSID). Подходит для монолитных приложений, где клиент и сервер на одном домене, или при использовании cookie.

session_start();

// Вход
if ($password_verify) {
    $_SESSION['user_id'] = $user['id'];
    $_SESSION['username'] = $user['username'];
    echo json_encode(['message' => 'ok']);
}

// Проверка
function requireAuth() {
    if (!isset($_SESSION['user_id'])) {
        http_response_code(401);
        echo json_encode(['error' => 'Не авторизован']);
        exit;
    }
}

Проблемы:

  • Не подходит для мобильных приложений или SPA, работающих на другом домене (CORS, CSRF).
  • Сервер хранит состояние - сложно масштабировать горизонтально (требуется общее хранилище сессий, например Redis).
  • Уязвимость к CSRF-атакам - необходимы токены для форм.

Как использовать API-ключи для аутентификации?

Простой вариант для серверных приложений или микросервисов. Клиент передаёт ключ в заголовке или параметре запроса.

$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? $_GET['api_key'] ?? '';
$validKeys = ['key1_secret', 'key2_secret']; // хранить в базе

if (!in_array($apiKey, $validKeys)) {
    http_response_code(403);
    echo json_encode(['error' => 'Неверный ключ']);
    exit;
}

Недостатки:

  • Ключ передаётся открыто - используйте HTTPS.
  • Нет привязки к конкретному пользователю - только к приложению.
  • Сложно отозвать отдельный ключ без изменения списка.

Как реализовать OAuth2 с входом через Google?

Стандартный протокол для делегирования аутентификации. Клиент получает токен доступа от стороннего провайдера.

// Используем библиотеку league/oauth2-google
$provider = new \League\OAuth2\Client\Provider\Google([
    'clientId'     => 'ВАШ_CLIENT_ID',
    'clientSecret' => 'ВАШ_SECRET',
    'redirectUri'  => 'https://ваш-сайт/callback',
]);

// Перенаправление на Google
if (!isset($_GET['code'])) {
    $authUrl = $provider->getAuthorizationUrl();
    $_SESSION['oauth2state'] = $provider->getState();
    header('Location: ' . $authUrl);
    exit;
}

// Получение токена
$token = $provider->getAccessToken('authorization_code', [
    'code' => $_GET['code']
]);
echo json_encode(['access_token' => $token->getToken()]);

Сложности:

  • Необходимость регистрации приложения у провайдера.
  • Хранение refresh token для долгосрочного доступа.
  • Обработка ошибок (отказ пользователя, истекший код).

Как сделать HMAC-подпись запросов?

Безопасный способ для серверных интеграций: запрос подписывается секретным ключом без его передачи.

// Сервер: проверка подписи
$secret = 'shared_secret';
$signature = hash_hmac('sha256', $requestBody, $secret);
$clientSignature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
if (!hash_equals($signature, $clientSignature)) {
    http_response_code(401);
    exit;
}

Внимание: Необходимо защищать от replay-атак добавлением временной метки и nonce.

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

Пример 1: Полный класс JWT-аутентификации

Пример
class Auth {
    private static $secretKey = 'A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6';
    private static $algorithm = 'HS256';
    private static $expire = 3600;

    public static function login($username, $password) {
        // проверка в БД (упрощено)
        $user = ['id' => 1, 'password_hash' => password_hash('pass123', PASSWORD_BCRYPT)];
        if (!password_verify($password, $user['password_hash'])) {
            return null;
        }
        $payload = [
            'iss' => 'example.com',
            'iat' => time(),
            'exp' => time() + self::$expire,
            'sub' => $user['id'],
            'username' => $username
        ];
        return JWT::encode($payload, self::$secretKey, self::$algorithm);
    }

    public static function validate($token) {
        try {
            return JWT::decode($token, new Key(self::$secretKey, self::$algorithm));
        } catch (\Exception $e) {
            return null;
        }
    }
}

Результат успешного входа:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJleGFtcGxlLmNvbSIsImlhdCI6MTY5ODc2NTQzMiwiZXhwIjoxNjk4NzY5MDMyLCJzdWIiOjEsInVzZXJuYW1lIjoidXNlcjEifQ.s9I4aF6iG0k7Rc9Xz2A3bPqZ7uQ1vW5yJ0dL8kBn3YQ

Пример 2: Сессионный API с Redis

Пример
// Настройка Redis как хранилище сессий
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379');
session_start();

// Авторизация
$_SESSION['user'] = ['id' => 1, 'role' => 'admin'];

// Возвращаем ID сессии
$sessionId = session_id();
echo json_encode(['session_id' => $sessionId]);

Ответ клиента:

{"session_id": "abc123xyz..."}

Клиент передаёт этот ID в cookie PHPSESSID при каждом запросе.

Пример 3: OAuth2 - получение профиля Google

Пример
// После получения access_token от Google
$resourceOwner = $provider->getResourceOwner($token);
$user = $resourceOwner->toArray();
echo json_encode([
    'email' => $user['email'],
    'name'  => $user['name'],
    'avatar' => $user['picture']
]);

Результат:

{"email": "user@gmail.com", "name": "Иван Иванов", "avatar": "https://..."}

Пример 4: HMAC-подпись с временной меткой

Пример
// Клиент формирует подпись
$data = ['action' => 'getUser', 'timestamp' => time()];
$secret = 'shared_secret';
$signature = hash_hmac('sha256', json_encode($data), $secret);

// Отправляет POST запрос с заголовками X-Signature, X-Timestamp
// Сервер проверяет
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'];
if (abs(time() - $timestamp) > 300) { // 5 минут
    die('Устаревший запрос');
}
$payload = file_get_contents('php://input');
$expected = hash_hmac('sha256', $payload, 'shared_secret');
if (hash_equals($expected, $_SERVER['HTTP_X_SIGNATURE'])) {
    echo json_encode(['status' => 'ok']);
} else {
    http_response_code(403);
}

Примечание: hash_equals защищает от атак по времени.

API входа на PHP - comments

En
Api login php (php)