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

web-token (JWT Framework)

JWT Framework (web-token) Флорана Морселли — полная реализация JOSE для PHP и Symfony: AlgorithmManager в роли списка разрешённых алгоритмов, отдельные классы проверки утверждений, все алгоритмы подписи из RFC 7518 и EdDSA — но без ES256K.
Лицензия
MIT
Проверенная версия
4.2.3 ·
Исходный код
github.com/web-token/jwt-framework
Документация
web-token.spomky-labs.com/

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

JOSE и JWT

Алгоритмы JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

web-token реализует JWS, JWE, JWK и наборы JWK как отдельные компоненты; web-token/jwt-library объединяет их в одном пакете, а бандл для Symfony регистрирует их как сервисы. Это выбор для PHP, когда нужны JWE, управление наборами JWK или полный список алгоритмов: в версии 4.2.3 подпись и проверка работали для HS, RS, PS и ES в вариантах 256/384/512, а также для EdDSA, включая PS384/512 и ES512, которых нет в firebase/php-jwt. Классов алгоритмов для ES256K и для полностью определённого Ed25519 нет.

Алгоритмы разрешаются явно: AlgorithmManager, переданный в JWSVerifier, и есть список разрешённых алгоритмов, а токен с любым другим алгоритмом отклоняется с ошибкой The algorithm "RS256" is not supported. Класс алгоритма none существует, но этот алгоритм принимается, только если вы сами добавите его в менеджер. Поддельный токен HS256, подписанный текстом PEM ключа EC, не прошёл проверку даже при разрешённом HS256: JWK ключа EC нельзя использовать как ключ HMAC. При ES256 с ключом P-384 получилась подпись, которую отверг собственный код проверки той же библиотеки.

Проверка подписи и проверка утверждений — отдельные шаги: JWSVerifier::verifyWithKey() вернул true для токена, срок действия которого истёк час назад. После этого запустите ClaimCheckerManager, перечислите обязательные утверждения — иначе exp не считается обязательным — и передайте допуск на расхождение часов в ExpirationTimeChecker, которому начиная с версии 4 нужен ещё и объект часов PSR-20; вся последовательность показана в примере.

Подписи ES256 — это 64 байта в формате raw r‖s, а с подписью в DER verifyWithKey() возвращает false; какой формат в токене, покажет отладчик JWT. Ключи везде представлены объектами JWK: JWKFactory создаёт их из PEM, сертификатов или параметров ключа, а конструктор JWKS формирует наборы ключей в том же формате.

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

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

  • Алгоритмы JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    Подпись и проверка работают для HS, RS, PS и ES в вариантах 256/384/512 и для EdDSA; классов алгоритмов ES256K и Ed25519 нет.

    Что записала проверка
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA
    evidence jws-algorithms: ES256K: class Jose\Component\Signature\Algorithm\ES256K exists: false
    evidence jws-algorithms: Ed25519: class Jose\Component\Signature\Algorithm\Ed25519 exists: false
  • Кодировка подписи ES256

    raw

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

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

    rejected

    С подписью ES256 в кодировке DER verifyWithKey() возвращает false.

    Что записала проверка
    fact jws-der-signature = rejected
    evidence jws-der-signature: false
  • Список разрешённых алгоритмов

    required

    Список разрешённых алгоритмов задаёт AlgorithmManager: проверка токена RS256 менеджером, в котором есть только ES256, завершилась ошибкой The algorithm "RS256" is not supported.

    Что записала проверка
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: RS256 token with an AlgorithmManager holding only ES256: InvalidArgumentException: The algorithm "RS256" is not supported.
  • Неподписанные токены (alg none)

    opt-in

    Неподписанный токен отклоняется с ошибкой The algorithm "none" is not supported., если не добавить в менеджер класс алгоритма None.

    Что записала проверка
    fact jwt-alg-none = opt-in
    evidence jwt-alg-none: with an ES256 AlgorithmManager: InvalidArgumentException: The algorithm "none" is not supported.; a None algorithm class exists: true
  • Открытый ключ в роли секрета HMAC

    rejected

    Когда разрешены и ES256, и HS256, токен HS256, подписанный текстом PEM в качестве ключа, не прошёл проверку с JWK ключа EC.

    Что записала проверка
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: AlgorithmManager [ES256, HS256] with the EC JWK: false
  • Сверка кривой с alg

    enforced

    Подписать ES256 ключом P-384 удалось, но код проверки библиотеки отверг результат.

    Что записала проверка
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: ES256 with a P-384 key, signing: ok; verifying: false
  • Проверка утверждений при декодировании

    no

    JWSVerifier::verifyWithKey() вернул true для токена, срок действия которого истёк час назад; утверждения проверяет отдельный ClaimCheckerManager.

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

    0s

    ExpirationTimeChecker не допускает расхождения часов, если допуск не задан: токен, срок действия которого истёк 3 секунды назад, отклонён.

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

    no

    Пока exp не входил в список обязательных утверждений, токен без него проходил проверку; когда его добавили в этот список, было выброшено исключение MissingMandatoryClaimException.

    Что записала проверка
    fact jwt-exp-required = no
    evidence jwt-exp-required: ExpirationTimeChecker without exp: ok; with exp listed as mandatory: Jose\Component\Checker\MissingMandatoryClaimException: The following claims are mandatory: exp.

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

Подпишите токен алгоритмом ES256 со списком разрешённых алгоритмов в AlgorithmManager, проверьте подпись, а затем утверждения exp и iss с помощью классов проверки утверждений.
<?php
require 'vendor/autoload.php';

use Jose\Component\Checker\{ClaimCheckerManager, ExpirationTimeChecker, IssuerChecker};
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\Algorithm\ES256;
use Jose\Component\Signature\{JWSBuilder, JWSVerifier};
use Jose\Component\Signature\Serializer\CompactSerializer;

$jwk = JWKFactory::createECKey('P-256');
$algorithms = new AlgorithmManager([new ES256()]); // the allow-list
$payload = json_encode(['sub' => 'user-42', 'iss' => 'https://auth.example.com', 'exp' => time() + 600]);
$jws = (new JWSBuilder($algorithms))->create()->withPayload($payload)->addSignature($jwk, ['alg' => 'ES256'])->build();
$token = (new CompactSerializer())->serialize($jws, 0);

// The signature check does not look at claims: run the claim checkers as a second step.
$parsed = (new CompactSerializer())->unserialize($token);
$valid = (new JWSVerifier($algorithms))->verifyWithKey($parsed, $jwk->toPublic(), 0);
$clock = new class implements Psr\Clock\ClockInterface { public function now(): DateTimeImmutable { return new DateTimeImmutable(); } };
$claims = json_decode($parsed->getPayload(), true);
(new ClaimCheckerManager([new ExpirationTimeChecker($clock, 30), new IssuerChecker(['https://auth.example.com'])]))->check($claims, ['exp', 'iss']);
echo 'valid: ', var_export($valid, true), ', sub: ', $claims['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 web-token/jwt-library:4.2.3
Команда
php usage.php
Вывод должен совпадать (регулярное выражение)
valid: true, sub: user-42

Библиотеки