Saltar al contenido
ecdsa.com
PHP

web-token (JWT Framework)

JWT Framework (web-token), de Florent Morselli, la implementación completa de JOSE para PHP y Symfony: un AlgorithmManager que hace de lista de permitidos, comprobadores de claims independientes y todos los algoritmos de firma del RFC 7518 más EdDSA, pero sin ES256K.
Licencia
MIT
Versión comprobada
4.2.3 ·
Código fuente
github.com/web-token/jwt-framework
Documentación
web-token.spomky-labs.com/

Compatibilidad de un vistazo

JOSE y JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

web-token implementa JWS, JWE, JWK y los conjuntos de JWK como componentes separados; web-token/jwt-library los agrupa, y un bundle de Symfony los integra como servicios. Es la opción de PHP cuando necesitas JWE, gestión de conjuntos de JWK o la lista completa de algoritmos: la 4.2.3 firmó y verificó HS, RS, PS y ES de 256/384/512 y EdDSA, incluidos PS384/512 y ES512, que no tiene firebase/php-jwt. No hay ninguna clase de algoritmo para ES256K ni para el Ed25519 totalmente especificado.

Los algoritmos se permiten de forma explícita: el AlgorithmManager que se pasa a JWSVerifier es la lista de permitidos, y un token con cualquier otro algoritmo falla con The algorithm "RS256" is not supported. none existe como clase de algoritmo, pero solo se acepta si lo incluyes en el gestor. Un token HS256 falsificado con el texto PEM de la clave EC falló incluso con HS256 permitido, porque una JWK EC no puede usarse como clave HMAC. ES256 con una clave P-384 produjo una firma que el propio verificador de la biblioteca rechazó.

La verificación y la comprobación de los claims son pasos separados: JWSVerifier::verifyWithKey() devolvió true para un token que había caducado una hora antes. Ejecuta después un ClaimCheckerManager, enumera los claims obligatorios —de lo contrario, exp no es obligatorio— y pasa una tolerancia a ExpirationTimeChecker, que desde la versión 4 también necesita un reloj PSR-20; el ejemplo muestra la secuencia completa.

Las firmas ES256 son 64 bytes de r‖s en bruto, y una firma DER hace que verifyWithKey() devuelva false; el decodificador de JWT muestra cuál de las dos lleva un token. Las claves son siempre objetos JWK; JWKFactory los construye a partir de PEM, de certificados o de parámetros de clave, y el constructor de JWKS genera conjuntos de claves en el mismo formato.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/web-token-jwt-framework/run.sh ejecutó web-token (JWT Framework) 4.2.3 en la imagen de contenedor php:8.5.11-cli-trixie el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Algoritmos JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    Con HS, RS, PS y ES de 256/384/512 y EdDSA se firmó y se verificó; no hay clases de algoritmo para ES256K ni para Ed25519.

    Registrado por la comprobación
    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
  • Codificación de la firma ES256

    raw

    Las firmas ES256 son 64 bytes de r‖s en bruto.

    Registrado por la comprobación
    fact jws-ecdsa-encoding = raw
  • Firma DER dentro de un JWS

    rejected

    Una firma ES256 codificada en DER hace que verifyWithKey() devuelva false.

    Registrado por la comprobación
    fact jws-der-signature = rejected
    evidence jws-der-signature: false
  • Lista de algoritmos permitidos

    required

    El AlgorithmManager es la lista de permitidos: un token RS256 contra un gestor que solo contenía ES256 falló con The algorithm "RS256" is not supported.

    Registrado por la comprobación
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: RS256 token with an AlgorithmManager holding only ES256: InvalidArgumentException: The algorithm "RS256" is not supported.
  • Tokens sin firma (alg none)

    opt-in

    Un token sin firmar falla con The algorithm "none" is not supported. salvo que se añada al gestor la clase de algoritmo None.

    Registrado por la comprobación
    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
  • Clave pública usada como secreto HMAC

    rejected

    Con ES256 y HS256 permitidos a la vez, un token HS256 con el texto PEM como clave falló contra la JWK EC.

    Registrado por la comprobación
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: AlgorithmManager [ES256, HS256] with the EC JWK: false
  • Curva cotejada con alg

    enforced

    La firma ES256 con una clave P-384 se completó, pero el verificador de la biblioteca rechazó el resultado.

    Registrado por la comprobación
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: ES256 with a P-384 key, signing: ok; verifying: false
  • Claims validados al decodificar

    no

    JWSVerifier::verifyWithKey() devolvió true para un token caducado una hora antes; los claims los comprueba un ClaimCheckerManager aparte.

    Registrado por la comprobación
    fact jwt-claims-validated-on-decode = no
    evidence jwt-claims-validated-on-decode: JWSVerifier on a token expired an hour ago: ok
  • Desfase de reloj predeterminado

    0s

    ExpirationTimeChecker no admite ninguna tolerancia salvo que se le pase una: se rechazó un token caducado tres segundos antes.

    Registrado por la comprobación
    fact jwt-clock-skew = 0s
  • Claim de caducidad obligatorio

    no

    Sin exp entre los claims obligatorios, un token que no lo tenía pasó la comprobación; al incluirlo, se lanzó MissingMandatoryClaimException.

    Registrado por la comprobación
    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.

Uso

Firma con ES256 mediante la lista de permitidos de un AlgorithmManager, verifica la firma y después comprueba exp e iss con comprobadores de claims.
<?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";

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor php:8.5.11-cli-trixie, y la ejecución falla si la salida no coincide.

Configuración
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
Comando
php usage.php
La salida debe coincidir (expresión regular)
valid: true, sub: user-42

Bibliotecas