Перейти к содержимому
ecdsa.com
PHP

lcobucci/jwt

lcobucci/jwt Луиса Кобуччи — библиотека JWT, на которой построены Laravel Passport и многие PHP-фреймворки: каждая проверка задаётся явным ограничением, assert() без ограничений не запускается, а длина ключей проверяется — но RSA-PSS нет.
Лицензия
BSD-3-Clause
Проверенная версия
5.6.1 ·
Исходный код
github.com/lcobucci/jwt
Документация
lcobucci-jwt.readthedocs.io/en/stable/

Совместимость вкратце

JOSE и JWT

Алгоритмы JWS
HS256HS384HS512RS256RS384RS512ES256ES384ES512EdDSA

lcobucci/jwt разделяет разбор, подпись и проверку: Configuration хранит объект подписи и ключи, parser()->parse() читает токен, ничего не проверяя, а validator()->assert() применяет переданные вами ограничения. Вызов assert() без ограничений выбрасывает NoConstraintsGiven, поэтому случайно написать проверку, которая ничего не проверяет, трудно.

SignedWith фиксирует объект подписи и ключ: и токен RS256, и токен HS256, подделанный с помощью текста PEM ключа EC, не прошли проверку с ошибкой Token signer mismatch. Неподписанные токены не удаётся даже разобрать (The JWT string is missing the Signature part). Размеры ключей проверяются уже при подписи: ES256 отклонил ключ P-384 (The length of the provided key is different than 256 bits), а HS256 — 40-битный ключ, так что некорректные токены вообще не создаются.

Проверки времени — тоже ограничения, поэтому они выполняются, только если их указать: токен, срок действия которого истёк час назад, прошёл проверку, когда было указано только SignedWith. LooseValidAt проверяет exp, nbf и iat, если они есть, и принял токен без exp; StrictValidAt требует все три утверждения — токен без iat не прошёл проверку с ошибкой "Issued At" claim missing, а токен с iat и exp, но без nbf — с ошибкой "Not Before" claim missing. Оба принимают необязательный интервал допуска на расхождение часов, по умолчанию нулевой. Пример задаёт все три утверждения и одним вызовом проверяет подпись, издателя и срок действия в строгом режиме.

Версия 5.6.1 поддерживает HS, RS и ES на 256/384/512 и EdDSA; классов подписи PS* и ES256K нет, поэтому для токенов от издателей, которые используют RSA-PSS, нужен web-token. Подписи ES256 — 64 байта в формате raw r‖s, а подпись DER не проходит проверку с ошибкой Token signature mismatch; какая кодировка в токене, покажет отладчик JWT.

Факты и проверки, на которых они основаны

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/lcobucci-jwt/run.sh запустила lcobucci/jwt 5.6.1 в образе контейнера php:8.5.11-cli-trixie; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Алгоритмы JWS

    HS256HS384HS512RS256RS384RS512ES256ES384ES512EdDSA

    Подпись и проверка работают для HS, RS и ES на 256/384/512 и для EdDSA; классов подписи для RSA-PSS и ES256K нет.

    Что записала проверка
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, EdDSA
    evidence jws-algorithms: PS256 signer class Lcobucci\JWT\Signer\Rsa\Pss\Sha256 exists: false
    evidence jws-algorithms: ES256K signer class Lcobucci\JWT\Signer\Ecdsa\Sha256K exists: false
  • Кодировка подписи ES256

    raw

    Подписи ES256 — 64 байта в формате raw r‖s.

    Что записала проверка
    fact jws-ecdsa-encoding = raw
  • Подпись в DER внутри JWS

    rejected

    Подпись ES256 в кодировке DER не проходит проверку с ошибкой Token signature mismatch.

    Что записала проверка
    fact jws-der-signature = rejected
    evidence jws-der-signature: Lcobucci\JWT\Validation\RequiredConstraintsViolated: The token violates some mandatory constraints, details: - Token signature mismatch
  • Список разрешённых алгоритмов

    required

    SignedWith фиксирует объект подписи: проверка токена RS256 с объектом подписи ES256 завершилась ошибкой Token signer mismatch, а assert() без ограничений выбрасывает NoConstraintsGiven.

    Что записала проверка
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: RS256 token with SignedWith(ES256 signer, EC key): Lcobucci\JWT\Validation\RequiredConstraintsViolated: The token violates some mandatory constraints, details: - Token signer mismatch
    evidence jwt-algorithm-allowlist: assert() with no constraints: Lcobucci\JWT\Validation\NoConstraintsGiven: No constraint given.
  • Неподписанные токены (alg none)

    rejected

    Неподписанный токен не удаётся разобрать: The JWT string is missing the Signature part.

    Что записала проверка
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: parsing an unsigned token: Lcobucci\JWT\Token\InvalidTokenStructure: The JWT string is missing the Signature part
  • Открытый ключ в роли секрета HMAC

    rejected

    Токен HS256, подписанный текстом PEM в качестве ключа, не прошёл SignedWith с объектом подписи ES256: возникла ошибка Token signer mismatch.

    Что записала проверка
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: SignedWith(ES256 signer, EC key): Lcobucci\JWT\Validation\RequiredConstraintsViolated: The token violates some mandatory constraints, details: - Token signer mismatch
  • Сверка кривой с alg

    enforced

    Объект подписи ES256 отклонил ключ P-384: The length of the provided key is different than 256 bits, 384 bits provided.

    Что записала проверка
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: ES256 signer with a P-384 key: Lcobucci\JWT\Signer\InvalidKeyProvided: The length of the provided key is different than 256 bits, 384 bits provided
  • Проверка утверждений при декодировании

    no

    Разбор и SignedWith не проверяют время: токен, срок действия которого истёк час назад, прошёл проверку.

    Что записала проверка
    fact jwt-claims-validated-on-decode = no
    evidence jwt-claims-validated-on-decode: SignedWith only, token expired an hour ago: ok
  • Допустимое расхождение часов по умолчанию

    0s

    LooseValidAt и StrictValidAt не допускают расхождения часов, если не передать им интервал.

    Что записала проверка
    fact jwt-clock-skew = 0s
  • Обязательное утверждение о сроке действия

    no

    LooseValidAt принял токен без exp; StrictValidAt требует iat, nbf и exp.

    Что записала проверка
    fact jwt-exp-required = no
    evidence jwt-exp-required: LooseValidAt without exp: ok; StrictValidAt: Lcobucci\JWT\Validation\RequiredConstraintsViolated: The token violates some mandatory constraints, details: - "Issued At" claim missing
    evidence jwt-exp-required: StrictValidAt with iat and exp but no nbf: Lcobucci\JWT\Validation\RequiredConstraintsViolated: The token violates some mandatory constraints, details: - "Not Before" claim missing
  • Короткий секрет HMAC

    rejected

    HS256 отклонил 5-байтовый ключ: Key provided is shorter than 256 bits, only 40 bits provided.

    Что записала проверка
    fact jwt-hmac-short-secret = rejected
    evidence jwt-hmac-short-secret: 5-byte HS256 key: Lcobucci\JWT\Signer\InvalidKeyProvided: Key provided is shorter than 256 bits, only 40 bits provided

Использование

Выпустите токен ES256 и проверьте подпись, издателя и срок действия в строгом режиме с допуском на расхождение часов в 30 секунд.
<?php
require 'vendor/autoload.php';

use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Ecdsa\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Validation\Constraint\{IssuedBy, SignedWith, StrictValidAt};

$key = openssl_pkey_new(['private_key_type' => OPENSSL_KEYTYPE_EC, 'curve_name' => 'prime256v1']);
openssl_pkey_export($key, $privatePem);
$private = InMemory::plainText($privatePem);
$public = InMemory::plainText(openssl_pkey_get_details($key)['key']);

$config = Configuration::forAsymmetricSigner(new Sha256(), $private, $public);
$now = new DateTimeImmutable();
$token = $config->builder()->issuedBy('https://auth.example.com')->relatedTo('user-42')
    ->issuedAt($now)->canOnlyBeUsedAfter($now)->expiresAt($now->modify('+10 minutes'))
    ->getToken($config->signer(), $config->signingKey());

// Every check is an explicit constraint; StrictValidAt requires iat, nbf and exp.
$parsed = $config->parser()->parse($token->toString());
$config->validator()->assert($parsed,
    new SignedWith($config->signer(), $config->verificationKey()),
    new IssuedBy('https://auth.example.com'),
    new StrictValidAt(SystemClock::fromUTC(), new DateInterval('PT30S')));
echo 'sub: ', $parsed->claims()->get('sub'), "\n";

Тестовый стенд для фрагментов кода запускает каждый пример без изменений в образе контейнера php:8.5.11-cli-trixie, и запуск завершается ошибкой, если вывод не совпадает.

Подготовка
curl -fsSL -o /tmp/composer.phar https://getcomposer.org/download/2.10.3/composer.phar && echo '7a2d379d5b8ffdaa028580ef26494c36d2feef4b178d3dd1473a4dbc5e17c8d6 /tmp/composer.phar' | sha256sum -c - >/dev/null && apt-get -qq update >/dev/null && apt-get -qq install -y unzip >/dev/null 2>&1 && COMPOSER_ALLOW_SUPERUSER=1 php /tmp/composer.phar require --quiet lcobucci/jwt:5.6.1 lcobucci/clock:3.6.0
Команда
php usage.php
Вывод должен совпадать (регулярное выражение)
sub: user-42

Библиотеки