Модель работы
JWT — это не шифрование и не секрет. Это подписанное, читаемое утверждение: любой может увидеть, что в нём написано, но только владелец ключа может доказать его подлинность. Держите это в голове — и каждое правило в этой библиотеке становится следствием.
Три точки, три части
Токен — это всего лишь три строки в base64url, соединённые точками:
заголовок . payload . подпись
│ │ │
│ │ └─ доказывает, что первые две части не менялись
│ └────────────── claims (sub, exp, произвольные поля)
└──────────────────────────── какой алгоритм подписал (alg, typ, kid)Раскодируйте первые две части — и вы сможете их прочитать: они не зашифрованы, а
лишь закодированы в base64url (не зашифрованы). Самое интересное — подпись: она
вычисляется по header.payload с ключом, поэтому изменение хотя бы одного байта в
любой из частей делает её недействительной.
JWT подписан, а не запечатан
Никогда не кладите секреты (пароли, номера карт) в payload — любой, у кого есть токен, может прочитать claims. JWT защищает от подмены, а не от чтения.
Единственное правило: ключ для подписи и ключ для проверки
Всё, что касается того, какой ключ куда идёт, сводится к семейству алгоритма:
- HMAC (
HS*) — симметричный: один секрет делает обе работы. Сторона, которая подписывает, и сторона, которая проверяет, используют одну и ту же строку. Хорошо подходит, когда это одна и та же сторона (ваш собственный API, выпускающий токены для себя). - RSA / ECDSA (
RS*/ES*) — асимметричные: приватный ключ подписывает, публичный ключ проверяет. Приватный ключ вы храните у себя; публичный можете раздать кому угодно. Хорошо подходит, когда другие должны проверять токены, которые выпустили вы (или когда вы проверяете токены, выпущенные провайдером).
Именно это различие — причина, по которой в библиотеке два отдельных типа:
use Flytachi\Jwt\Entity\PrivateKey;
use Flytachi\Jwt\Entity\PublicKey;
// Сторона подписи — encode() всегда принимает только PrivateKey
new PrivateKey($secretOrPrivatePem, 'HS256');
// Сторона проверки — decode() всегда принимает только PublicKey
new PublicKey($secretOrPublicPem, 'HS256');Для HMAC «приватный» и «публичный» ключ хранят одну и ту же секретную строку — имена типов описывают роль, а не то, что материал ключа различается.
Ключ привязан к своему алгоритму
PrivateKey и PublicKey каждый несёт в себе алгоритм, и decode() требует, чтобы
алгоритм ключа для проверки совпадал с тем, что назван в заголовке токена. Это не
бюрократия — это защита от классической атаки «путаница алгоритмов», при которой
злоумышленник берёт ваш публичный ключ RSA (который, что логично, публичен) и пытается
использовать его как секрет HMAC. Поскольку ключ привязан к RS256, токен, заявляющий
HS256, отклоняется ещё до того, как вообще запустится проверка.
Вот почему
Каждая ошибка «алгоритм должен совпадать» восходит к этой привязке. Вы выбираете алгоритм при создании ключа, и токен не может вас переубедить.
Что происходит при encode()
Мягко, от начала до конца:
- Заголовок строится из алгоритма ключа (и его
kid, если задан). - Заголовок и payload кодируются в JSON, затем в base64url.
header.payloadподписывается приватным ключом — HMAC дляHS*,openssl_signдляRS*/ES*.- Подпись кодируется в base64url и добавляется в конец. Вы получаете
header.payload.signature.
Что происходит при decode()
- Токен разбивается на три части (неверное количество → отклонение).
- Читается заголовок, чтобы узнать алгоритм — и, для асимметричных токенов,
kid. - Выбирается нужный
PublicKey: единственный ключ для HMAC или ключ, совпавший поkid, для RSA/ECDSA. - Алгоритм ключа сверяется с заголовком (привязка, описанная выше).
- Проверяется подпись — с помощью
hash_equalsдля HMAC, поэтому сравнение идёт за постоянное время и не утекает никакой информации о таймингах. - Наконец, проверяются временные claims (
exp,nbf,iat) с учётом допуска (leeway).
Только если всё это проходит, вы получаете обратно JwtPayload. Подробная версия —
преобразование ECDSA DER, сборка PEM из JWKS, защита от атак по таймингам — описана в
разделе Модель безопасности.
Когда выбирать какое семейство
- ✅ HMAC — ваш сервис выпускает токены и сам же их проверяет. Проще всего, быстрее всего.
- ✅ RSA / ECDSA — сторонние участники (или отдельные сервисы) должны проверять токены без вашего секрета для подписи, либо вы проверяете токены провайдера через JWKS.
- ❌ Хранение секретов в payload — он читаем. Используйте claim как идентификатор, а затем подгружайте чувствительные данные на стороне сервера.
Дальше
- Попробовать на практике: Быстрый старт
- Рецепты: HMAC-токены · Асимметричные ключи
- Авторитетная поверхность: Справочник API