Пакет · jwt

Быстрый старт

От установки до проверенного токена за пять минут: вы выпустите подписанный JWT для вошедшего пользователя, вернёте его при следующем запросе и проверите, прежде чем довериться хотя бы одному claim.

Время ~5 минУровень НачальныйТребования PHP 8.1+

Что вы построите

Небольшой поток «вход, затем доступ». При входе вы выпускаете токен, который сообщает, кто этот пользователь и когда токен истекает. При следующем запросе клиент отправляет его обратно, и вы проверяете его — подпись и срок действия — прежде чем прочитать идентификатор пользователя. Вот весь цикл: выпустить → отправить → проверить → прочитать.

Мы будем использовать HS256 (общий секрет), потому что для него не нужны файлы ключей. Переход на RSA или ECDSA позже меняет только ключ, но не поток — см. «Асимметричные ключи».

Прежде чем начать

Установите пакет:

bash
composer require flytachi/jwt

Шаг 1 — Выпустите токен при входе

Соберите JwtPayload с нужными вам claims, затем подпишите его с помощью PrivateKey.

login.php
<?php

require 'vendor/autoload.php';

use Flytachi\Jwt\JWT;
use Flytachi\Jwt\Entity\JwtPayload;
use Flytachi\Jwt\Entity\PrivateKey;

$secret = getenv('JWT_SECRET'); // загрузите его, никогда не прописывайте в коде

$now = time();
$token = JWT::encode(
  new JwtPayload([
      'iss' => 'https://my-app.com',   // кто его выпустил
      'sub' => 'user-42',              // о ком он
      'iat' => $now,                   // время выпуска
      'exp' => $now + 3600,            // истекает через один час
  ]),
  new PrivateKey($secret, 'HS256')
);

echo $token; // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Что это за claims?

iss, sub, iat, exp — это стандартные claims. exp и iat (а также nbf) проверяются автоматически при декодировании. Вы также можете добавить любой собственный claim — см. «Работа с claims».

Шаг 2 — Отправьте его обратно и проверьте

При следующем запросе клиент возвращает токен (обычно в заголовке Authorization: Bearer <token>). Проверьте его с помощью PublicKey, несущего тот же секрет и алгоритм.

api.php
<?php

require 'vendor/autoload.php';

use Flytachi\Jwt\JWT;
use Flytachi\Jwt\Entity\PublicKey;
use Flytachi\Jwt\JWTException;

$secret = getenv('JWT_SECRET');
$token  = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$token  = str_replace('Bearer ', '', $token);

try {
  $payload = JWT::decode($token, [new PublicKey($secret, 'HS256')]);

  $userId = $payload->getClaim('sub');
  echo "Authenticated as {$userId}\n";
} catch (JWTException $e) {
  http_response_code(401);
  echo "Invalid token: {$e->getMessage()}\n";
}

Проверка делает три вещи сразу

decode() проверяет подпись (обнаружение подмены), подтверждает, что алгоритм ключа совпадает с заголовком токена, и проверяет exp/nbf/iat. Любой сбой выбрасывает одно JWTException, поэтому один catch покрывает их все.

Шаг 3 — Убедитесь, что он отклоняет плохой токен

Докажите, что гарантии соблюдаются. Подмените токен или дождитесь его истечения:

php
// Токен, чей секрет не совпадает → проверка подписи не проходит
try {
  JWT::decode($token, [new PublicKey('wrong-secret', 'HS256')]);
} catch (JWTException $e) {
  echo $e->getMessage(); // Signature verification failed.
}

// Уже истёкший токен → проверка временного claim не проходит
$expired = JWT::encode(
  new JwtPayload(['sub' => 'user-42', 'exp' => time() - 10]),
  new PrivateKey($secret, 'HS256')
);
try {
  JWT::decode($expired, [new PublicKey($secret, 'HS256')]);
} catch (JWTException $e) {
  echo $e->getMessage(); // Token has expired (exp).
}

Всё целиком

Полный, готовый к копированию цикл:

jwt-demo.php
<?php

require 'vendor/autoload.php';

use Flytachi\Jwt\JWT;
use Flytachi\Jwt\Entity\JwtPayload;
use Flytachi\Jwt\Entity\PrivateKey;
use Flytachi\Jwt\Entity\PublicKey;
use Flytachi\Jwt\JWTException;

$secret = 'change-me-to-a-strong-secret';
$now = time();

// Выпуск
$token = JWT::encode(
  new JwtPayload([
      'iss' => 'https://my-app.com',
      'sub' => 'user-42',
      'iat' => $now,
      'exp' => $now + 3600,
  ]),
  new PrivateKey($secret, 'HS256')
);

// Проверка
try {
  $payload = JWT::decode($token, [new PublicKey($secret, 'HS256')]);
  echo "OK, user = " . $payload->getClaim('sub') . "\n";
} catch (JWTException $e) {
  echo "Rejected: " . $e->getMessage() . "\n";
}

Запустите его:

bash
php jwt-demo.php
# OK, user = user-42

Что только что произошло

  • encode() построил заголовок, закодировал заголовок и payload в base64url, подписал пару и объединил их в строку header.payload.signature, которую вы видели.
  • decode() обратил это: разбил токен, проверил подпись с помощью hash_equals, убедился, что алгоритм совпадает, и проверил временные claims.
  • Один секрет и подписал, и проверил, потому что HS256 симметричен. С RSA/ECDSA это становится двумя разными ключами.

Следующие шаги