Aller au contenu
ecdsa.com
PHP

lcobucci/jwt

lcobucci/jwt de Luís Cobucci, la bibliothèque JWT sur laquelle reposent Laravel Passport et de nombreux frameworks PHP : chaque contrôle est une contrainte explicite, assert() refuse de s’exécuter sans contrainte et les longueurs de clé sont imposées, mais RSA-PSS est absent.
Licence
BSD-3-Clause
Version vérifiée
5.6.1 ·
Code source
github.com/lcobucci/jwt
Documentation
lcobucci-jwt.readthedocs.io/en/stable/

Compatibilité en un coup d’œil

JOSE et JWT

Algorithmes JWS
HS256HS384HS512RS256RS384RS512ES256ES384ES512EdDSA

lcobucci/jwt sépare l’analyse, la signature et la validation : une Configuration contient le signataire et les clés, parser()->parse() lit un jeton sans rien contrôler, et validator()->assert() applique les contraintes que vous passez. Appeler assert() sans contrainte lève NoConstraintsGiven, ce qui rend difficile d’écrire par accident une validation qui ne contrôle rien.

SignedWith fixe le signataire et la clé : un jeton RS256 et un jeton HS256 forgé avec le texte PEM de la clé EC ont tous deux échoué avec Token signer mismatch. Les jetons non signés ne peuvent même pas être analysés (The JWT string is missing the Signature part). Les tailles de clé sont imposées dès la signature – ES256 a refusé une clé P-384 (The length of the provided key is different than 256 bits) et HS256 une clé de 40 bits –, ce qui empêche tout simplement de produire des jetons mal formés.

Les contrôles temporels sont eux aussi des contraintes et ne s’exécutent donc que s’ils sont listés : un jeton expiré depuis une heure a passé SignedWith seul. LooseValidAt contrôle exp, nbf et iat lorsqu’ils sont présents et a accepté un jeton sans exp ; StrictValidAt exige les trois (un jeton sans iat a échoué avec "Issued At" claim missing, et un jeton avec iat et exp mais sans nbf, avec "Not Before" claim missing). Les deux acceptent un intervalle de tolérance facultatif, nul par défaut. L’exemple définit les trois claims et vérifie la signature, l’émetteur et la validité stricte en un seul appel.

La 5.6.1 prend en charge HS, RS et ES en 256/384/512 ainsi qu’EdDSA ; il n’existe ni signataire PS*, ni ES256K, si bien que les jetons d’émetteurs qui utilisent RSA-PSS nécessitent web-token. Les signatures ES256 font 64 octets en r‖s brut, et une signature DER échoue avec Token signature mismatch ; le débogueur JWT montre quel encodage contient un jeton.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/lcobucci-jwt/run.sh a exécuté lcobucci/jwt 5.6.1 dans l’image de conteneur php:8.5.11-cli-trixie le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Algorithmes JWS

    HS256HS384HS512RS256RS384RS512ES256ES384ES512EdDSA

    Signature et vérification réussies avec HS, RS et ES en 256/384/512 ainsi qu’avec EdDSA ; il n’existe pas de classes de signataire RSA-PSS ni ES256K.

    Relevé par la vérification
    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
  • Encodage des signatures ES256

    raw

    Les signatures ES256 font 64 octets, en r‖s brut.

    Relevé par la vérification
    fact jws-ecdsa-encoding = raw
  • Signature DER dans un JWS

    rejected

    Une signature ES256 encodée en DER échoue avec Token signature mismatch.

    Relevé par la vérification
    fact jws-der-signature = rejected
    evidence jws-der-signature: Lcobucci\JWT\Validation\RequiredConstraintsViolated: The token violates some mandatory constraints, details: - Token signature mismatch
  • Liste d’algorithmes autorisés

    required

    SignedWith fixe le signataire : un jeton RS256 présenté à un signataire ES256 a échoué avec Token signer mismatch, et assert() sans contrainte lève NoConstraintsGiven.

    Relevé par la vérification
    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.
  • Jetons non signés (alg none)

    rejected

    Un jeton non signé ne peut pas être analysé : The JWT string is missing the Signature part.

    Relevé par la vérification
    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
  • Clé publique utilisée comme secret HMAC

    rejected

    Un jeton HS256 dont la clé est le texte PEM a échoué au contrôle SignedWith pour le signataire ES256 avec Token signer mismatch.

    Relevé par la vérification
    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
  • Courbe vérifiée par rapport à alg

    enforced

    Le signataire ES256 a refusé une clé P-384 : The length of the provided key is different than 256 bits, 384 bits provided.

    Relevé par la vérification
    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
  • Claims validés au décodage

    no

    L’analyse et SignedWith ne contrôlent pas la validité temporelle : un jeton expiré depuis une heure a été accepté.

    Relevé par la vérification
    fact jwt-claims-validated-on-decode = no
    evidence jwt-claims-validated-on-decode: SignedWith only, token expired an hour ago: ok
  • Décalage d’horloge toléré par défaut

    0s

    LooseValidAt et StrictValidAt n’accordent aucune marge de tolérance, sauf si vous leur fournissez un intervalle.

    Relevé par la vérification
    fact jwt-clock-skew = 0s
  • Claim d’expiration obligatoire

    no

    LooseValidAt a accepté un jeton sans exp ; StrictValidAt exige iat, nbf et exp.

    Relevé par la vérification
    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
  • Secret HMAC court

    rejected

    HS256 a refusé une clé de 5 octets : Key provided is shorter than 256 bits, only 40 bits provided.

    Relevé par la vérification
    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

Utilisation

Émettre un jeton ES256 et vérifier la signature, l’émetteur et la validité stricte avec 30 secondes de tolérance.
<?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";

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur php:8.5.11-cli-trixie, et l’exécution échoue si la sortie ne correspond pas.

Mise en place
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
Commande
php usage.php
La sortie doit correspondre à cette expression régulière
sub: user-42

Bibliothèques