В современном мире веб-разработки API — это основа любого мобильного приложения или SPA. А безопасность — головная боль каждого разработчика. JWT (JSON Web Token) стал стандартом де-факто для аутентификации в API. В этой статье я расскажу, как быстро и без боли настроить API на Yii2 с JWT-аутентификацией.

Digital-студия WNDER

Что такое JWT и зачем он нужен?

JWT — это JSON Web Token. Звучит страшно, но на деле это просто строка, которая содержит зашифрованную информацию о пользователе. Представьте, что вы даёте другу пропуск в клуб. В пропуске написано, кто вы и до какого времени действует пропуск. JWT работает так же: сервер выдаёт токен, клиент его хранит и при каждом запросе показывает. Сервер проверяет подпись и пускает или не пускает.

Главное преимущество JWT — он не требует хранения сессий на сервере. Всё, что нужно, уже внутри токена. Это удобно для масштабирования и микросервисов.

Установка и настройка Yii2

Предположим, у вас уже есть проект на Yii2. Если нет — создайте его через Composer:

composer create-project --prefer-dist yiisoft/yii2-app-basic api-jwt
cd api-jwt

Теперь установим библиотеку для работы с JWT. Есть несколько вариантов, но я рекомендую firebase/php-jwt — она простая и надёжная.

composer require firebase/php-jwt

Теперь настроим приложение для работы в режиме API. В файле config/web.php добавьте компонент request с парсером JSON:

'request' => [
    'parsers' => [
        'application/json' => 'yii\web\JsonParser',
    ],
],

И включите URL-менеджер для красивого роутинга:

'urlManager' => [
    'enablePrettyUrl' => true,
    'showScriptName' => false,
    'rules' => [
        'POST auth/login' => 'auth/login',
        'POST auth/signup' => 'auth/signup',
        'GET user/profile' => 'user/profile',
    ],
],

Создаём модель пользователя и контроллер аутентификации

Для начала создадим модель User. Если вы используете стандартную таблицу user из basic-шаблона, она нам подойдёт. Но убедитесь, что в ней есть поля username, password_hash и auth_key (последний не обязателен, но пригодится).

Теперь создадим контроллер AuthController с действиями login и signup.

<?php
namespace app\controllers;

use Yii;
use yii\rest\Controller;
use app\models\User;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use yii\web\Response;

class AuthController extends Controller
{
    public $enableCsrfValidation = false;

    public function actionLogin()
    {
        $request = Yii::$app->request;
        $username = $request->post('username');
        $password = $request->post('password');

        $user = User::findByUsername($username);

        if (!$user || !$user->validatePassword($password)) {
            Yii::$app->response->statusCode = 401;
            return ['error' => 'Неверный логин или пароль'];
        }

        $token = $this->generateJwt($user);
        return ['token' => $token];
    }

    public function actionSignup()
    {
        $request = Yii::$app->request;
        $user = new User();
        $user->username = $request->post('username');
        $user->setPassword($request->post('password'));
        $user->generateAuthKey();

        if ($user->save()) {
            $token = $this->generateJwt($user);
            return ['token' => $token];
        }

        Yii::$app->response->statusCode = 400;
        return ['errors' => $user->getErrors()];
    }

    private function generateJwt($user)
    {
        $payload = [
            'iss' => 'your-domain.com',
            'sub' => $user->id,
            'iat' => time(),
            'exp' => time() + 3600, // токен живёт 1 час
        ];

        return JWT::encode($payload, Yii::$app->params['jwtSecretKey'], 'HS256');
    }
}

Обратите внимание: в config/params.php нужно добавить секретный ключ:

'jwtSecretKey' => 'your-very-secret-key-change-me',

Защита API с помощью фильтра JWT

Теперь самое интересное: как защитить другие эндпоинты? В Yii2 для этого есть фильтры. Создадим фильтр JwtAuth.

<?php
namespace app\filters;

use Yii;
use yii\base\ActionFilter;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use app\models\User;

class JwtAuth extends ActionFilter
{
    public function beforeAction($action)
    {
        $authHeader = Yii::$app->request->headers->get('Authorization');
        if (!$authHeader) {
            Yii::$app->response->statusCode = 401;
            Yii::$app->response->data = ['error' => 'Требуется токен'];
            return false;
        }

        $token = str_replace('Bearer ', '', $authHeader);
        try {
            $decoded = JWT::decode($token, new Key(Yii::$app->params['jwtSecretKey'], 'HS256'));
            $user = User::findIdentity($decoded->sub);
            if (!$user) {
                throw new \Exception('Пользователь не найден');
            }
            Yii::$app->user->setIdentity($user);
        } catch (\Exception $e) {
            Yii::$app->response->statusCode = 401;
            Yii::$app->response->data = ['error' => 'Неверный токен: ' . $e->getMessage()];
            return false;
        }

        return parent::beforeAction($action);
    }
}

Теперь применим этот фильтр в контроллере, который должен быть защищён. Например, UserController:

class UserController extends Controller
{
    public $enableCsrfValidation = false;

    public function behaviors()
    {
        return [
            'jwtAuth' => [
                'class' => \app\filters\JwtAuth::class,
            ],
        ];
    }

    public function actionProfile()
    {
        return Yii::$app->user->identity;
    }
}

Теперь при запросе к /user/profile без токена вы получите 401, а с токеном — данные пользователя.

Правило: Никогда не храните секретный ключ JWT в репозитории. Используйте переменные окружения или локальный файл, который не попадает в Git.

Практические советы и лайфхаки

  • Время жизни токена. Не делайте его слишком большим. Час — оптимально. Для долгоживущих сессий используйте refresh-токены.
  • Хранение токена на клиенте. Лучше всего в httpOnly cookie, чтобы избежать XSS. Но если у вас SPA, можно использовать localStorage, но тогда защититесь от CSRF.
  • Обработка ошибок. Всегда возвращайте понятные сообщения об ошибках в JSON. Клиент должен знать, что пошло не так.
  • Тестирование. Используйте Postman или Insomnia для проверки эндпоинтов. Не забудьте про автоматические тесты.

Вот таблица с основными параметрами JWT, которые вы можете использовать в payload:

Параметр Описание Обязательный
iss Издатель токена (например, ваш домен) Нет
sub Идентификатор пользователя Да
iat Время выдачи токена (Unix timestamp) Нет
exp Время истечения токена (Unix timestamp) Да
nbf Токен действителен не раньше этого времени Нет

Ещё один лайфхак: если вы используете HTTPS (а вы должны!), то токен передаётся в заголовке Authorization: Bearer <token>. Это стандарт, и его поддерживают все клиенты.

Заключение

Мы разобрали, как создать API на Yii2 с JWT-аутентификацией. Это не так сложно, как кажется. Главное — понять принцип работы токенов и правильно настроить фильтры. Теперь вы можете защитить свои эндпоинты и спать спокойно.

Не забывайте обновлять токены, использовать HTTPS и не хранить секреты в открытом виде. Удачи в разработке!

© Студия WNDER