Вы когда-нибудь задумывались, как защитить свой API, чтобы только авторизованные пользователи могли получать данные? JWT — это как электронный пропуск, который выдается после логина и действует определенное время. В этой статье я покажу, как настроить JWT-аутентификацию в Yii2, используя простой и понятный подход. Вы научитесь создавать безопасный API, который будет удобен и для ваших клиентов, и для вас.

Digital-студия WNDER

Ссылка на оригинальную статью

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

JWT (JSON Web Token) — это компактный и безопасный способ передачи данных между клиентом и сервером. Представьте, что вы приходите в спортзал и получаете браслет с QR-кодом. Пока браслет действует, вы проходите без предъявления паспорта. JWT работает аналогично: после логина сервер выдает токен, и клиент отправляет его с каждым запросом. Сервер проверяет токен и, если он валиден, выполняет запрос.

Почему именно JWT, а не сессии? Сессии хранятся на сервере и занимают память, а JWT — самодостаточен: он содержит всю информацию о пользователе и подпись. Это идеально для API, где клиенты могут быть мобильными приложениями, SPA или другими серверами.

Настройка проекта Yii2

Для начала нам понадобится установленный Yii2. Если у вас его нет, создайте проект с помощью Composer:

composer create-project --prefer-dist yiisoft/yii2-app-advanced api-demo

Перейдите в папку проекта и установите расширение для JWT. Рекомендую использовать firebase/php-jwt — оно легкое и популярное.

composer require firebase/php-jwt

Теперь создадим базу данных и таблицу пользователей. Для простоты используем стандартную схему Yii2 с таблицей user, но добавим поле api_token для хранения токена (необязательно, если вы будете проверять только подпись). В этом руководстве мы будем генерировать токен на основе ID пользователя и секретного ключа, поэтому дополнительное поле не требуется.

Убедитесь, что у вас настроено подключение к БД в common/config/main-local.php.

Установка JWT-компонента в Yii2

Теперь настроим компонент JWT в конфигурации приложения. Откройте common/config/main.php и добавьте следующий код в секцию components:

'components' => [
    // ... другие компоненты
    'jwt' => [
        'class' => 'sizeg\jwt\Jwt',
        'key' => 'your-secret-key-here', // замените на свой секретный ключ
        'jwtValidationData' => [
            'class' => 'sizeg\jwt\JwtValidationData',
            'audience' => 'your-audience',
            'subject' => 'your-subject',
        ],
    ],
],

Здесь мы используем пакет sizeg/yii2-jwt, который удобно интегрируется с Yii2. Установите его вместо firebase, если еще не сделали:

composer require sizeg/yii2-jwt

Этот компонент автоматически проверяет токен при каждом запросе, если вы укажете его в поведении контроллера. Мы это сделаем чуть позже.

Создание контроллера для логина и получения токена

Создадим контроллер AuthController, который будет обрабатывать POST-запрос на /auth/login. Клиент отправляет логин и пароль, а контроллер проверяет их и возвращает JWT-токен.

Создайте файл api/modules/v1/controllers/AuthController.php (или в любом другом месте, где у вас API).

namespace api\modules\v1\controllers;

use Yii;
use yii\rest\Controller;
use api\modules\v1\models\User;
use sizeg\jwt\Jwt;
use sizeg\jwt\JwtHttpBearerAuth;

class AuthController extends Controller
{
    public $modelClass = 'api\modules\v1\models\User';

    public function behaviors()
    {
        $behaviors = parent::behaviors();
        // Отключаем аутентификацию для этого контроллера, кроме метода login
        $behaviors['authenticator'] = [
            'class' => JwtHttpBearerAuth::class,
            'optional' => ['login'],
        ];
        return $behaviors;
    }

    public function actionLogin()
    {
        $request = Yii::$app->request->post();
        $user = User::findByUsername($request['username']);
        if (!$user || !Yii::$app->security->validatePassword($request['password'], $user->password_hash)) {
            throw new \yii\web\UnauthorizedHttpException('Invalid credentials');
        }

        $jwt = Yii::$app->jwt;
        $token = $jwt->getBuilder()
            ->setIssuer('your-issuer') // кто выдал токен
            ->setAudience('your-audience') // для кого токен
            ->setId($user->id, true) // ID пользователя
            ->setIssuedAt(time()) // время выпуска
            ->setExpiration(time() + 3600) // срок действия 1 час
            ->sign($jwt->getSigner(), $jwt->getKey()) // подпись
            ->getToken();

        return [
            'token' => (string)$token,
        ];
    }
}

Обратите внимание: метод findByUsername нужно реализовать в модели User. Если вы используете стандартную модель из Yii2, добавьте такой метод:

public static function findByUsername($username)
{
    return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
}

Также не забудьте, что пароль должен быть захэширован с помощью Yii::$app->security->generatePasswordHash() при создании пользователя.

Защита API-контроллеров с помощью JWT

Теперь, когда у нас есть механизм выдачи токена, нужно защитить остальные контроллеры. Для этого в каждом контроллере, который требует аутентификации, добавьте поведение JwtHttpBearerAuth. Например, создадим контроллер ProfileController:

namespace api\modules\v1\controllers;

use yii\rest\ActiveController;
use sizeg\jwt\JwtHttpBearerAuth;

class ProfileController extends ActiveController
{
    public $modelClass = 'api\modules\v1\models\User';

    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator'] = [
            'class' => JwtHttpBearerAuth::class,
        ];
        return $behaviors;
    }

    // Действие для получения профиля текущего пользователя
    public function actionIndex()
    {
        $user = Yii::$app->user->identity;
        return $user;
    }
}

Теперь при обращении к /profile без токена вы получите ошибку 401. А если отправите токен в заголовке Authorization: Bearer <token>, то получите данные пользователя.

Настройка маршрутов и CORS

Для работы API нужно настроить правила маршрутизации в config/main.php модуля api. Добавьте в urlManager следующие правила:

'urlManager' => [
    'enablePrettyUrl' => true,
    'showScriptName' => false,
    'rules' => [
        [
            'class' => 'yii\rest\UrlRule',
            'controller' => 'v1/auth',
            'extraPatterns' => [
                'POST login' => 'login',
            ],
        ],
        [
            'class' => 'yii\rest\UrlRule',
            'controller' => 'v1/profile',
        ],
    ],
]

Не забудьте настроить CORS (Cross-Origin Resource Sharing), если ваш API будут использовать фронтенд-приложения с другого домена. Для этого можно использовать поведение yii\filters\Cors. Добавьте его в контроллер:

public function behaviors()
{
    $behaviors = parent::behaviors();
    $behaviors['corsFilter'] = [
        'class' => \yii\filters\Cors::class,
        'cors' => [
            'Origin' => ['*'],
            'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'DELETE'],
            'Access-Control-Allow-Headers' => ['Content-Type', 'Authorization'],
        ],
    ];
    return $behaviors;
}

Обратите внимание, что CORS должен быть настроен до аутентификации, иначе предварительные OPTIONS-запросы будут отклонены.

Проверка работы API с помощью Postman или cURL

Давайте протестируем. Сначала получим токен:

curl -X POST http://api-demo.local/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"demo","password":"demo123"}'

В ответ вы получите JSON с токеном. Скопируйте его и используйте для запроса профиля:

curl http://api-demo.local/v1/profile \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."

Если все настроено правильно, вы увидите данные пользователя. Если нет — проверьте логи и убедитесь, что секретный ключ в конфигурации совпадает с тем, что используется при подписи.

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

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

Вот несколько рекомендаций, которые помогут избежать типичных ошибок:

  • Устанавливайте короткий срок действия токена (например, 15-30 минут) и обновляйте его с помощью refresh-токена.
  • Используйте HTTPS в продакшене, чтобы токен не перехватили.
  • Для больших проектов рассмотрите использование OAuth2, но для большинства задач JWT достаточно.
  • Проверяйте не только подпись, но и срок действия токена. В компоненте sizeg/yii2-jwt это делается автоматически.
  • Логируйте ошибки аутентификации, чтобы быстро находить проблемы.

Сравнение JWT и обычных сессий

Критерий JWT Сессии
Хранение на сервере Не требуется, токен самодостаточен Требуется, данные хранятся в памяти или БД
Масштабируемость Отличная, подходит для распределенных систем Проблемы при горизонтальном масштабировании
Мобильные приложения Удобно, не нужны куки Сложнее, нужна поддержка кук

Заключение

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

Если вы хотите сэкономить время и заказать разработку API под ключ, обратитесь в Digital-студию WNDER — мы поможем с любыми задачами.

Студия WNDER — разработка и поддержка веб-проектов