Pular para o conteúdo
ecdsa.com
PHP

lcobucci/jwt

O lcobucci/jwt, de Luís Cobucci, a biblioteca JWT por trás do Laravel Passport e de muitos frameworks PHP: toda verificação é uma restrição explícita, assert() se recusa a rodar sem nenhuma, e os tamanhos de chave são impostos — mas não há RSA-PSS.
Licença
BSD-3-Clause
Versão verificada
5.6.1 ·
Código-fonte
github.com/lcobucci/jwt
Documentação
lcobucci-jwt.readthedocs.io/en/stable/

Visão geral da compatibilidade

JOSE e JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512ES256ES384ES512EdDSA

O lcobucci/jwt separa análise, assinatura e validação: uma Configuration guarda o signatário e as chaves, parser()->parse() lê um token sem verificar nada, e validator()->assert() aplica as restrições que você passa. Chamar assert() sem restrições lança NoConstraintsGiven, o que torna difícil escrever por acidente uma validação que não verifica nada.

SignedWith fixa o signatário e a chave: um token RS256 e um token HS256 forjado com o texto PEM da chave EC falharam, ambos, com Token signer mismatch. Tokens sem assinatura nem sequer podem ser analisados (The JWT string is missing the Signature part). Os tamanhos de chave são impostos no momento da assinatura — o ES256 recusou uma chave P-384 (The length of the provided key is different than 256 bits) e o HS256 recusou uma chave de 40 bits —, o que impede que tokens malformados sejam sequer produzidos.

As verificações de tempo também são restrições, então só rodam quando listadas: um token que tinha expirado uma hora antes passou só com SignedWith. LooseValidAt verifica exp, nbf e iat quando presentes e aceitou um token sem exp; StrictValidAt exige as três — um token sem iat falhou com "Issued At" claim missing, e um com iat e exp, mas sem nbf, com "Not Before" claim missing. Ambas aceitam um intervalo de tolerância opcional, zero por padrão. O exemplo define as três claims e valida assinatura, emissor e validade estrita em uma única chamada.

A versão 5.6.1 oferece suporte a HS, RS e ES em 256/384/512 e a EdDSA; não há signatários PS* nem ES256K, então tokens de emissores que usam RSA-PSS precisam do web-token. As assinaturas ES256 são 64 bytes de r‖s brutos, e uma assinatura DER falha com Token signature mismatch; o depurador de JWT mostra qual codificação um token usa.

Os fatos e as verificações por trás deles

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/lcobucci-jwt/run.sh executou lcobucci/jwt 5.6.1 na imagem de contêiner php:8.5.11-cli-trixie em 20 de outubro de 2026; a página precisa corresponder ao que a verificação imprimiu, e cada fato lista essas linhas.

  • Algoritmos JWS

    HS256HS384HS512RS256RS384RS512ES256ES384ES512EdDSA

    Assinatura e verificação funcionaram com HS, RS e ES em 256/384/512 e com EdDSA; não há classes de signatário para RSA-PSS nem para ES256K.

    Registrado pela verificação
    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
  • Codificação da assinatura ES256

    raw

    As assinaturas ES256 são 64 bytes de r‖s brutos.

    Registrado pela verificação
    fact jws-ecdsa-encoding = raw
  • Assinatura DER dentro de um JWS

    rejected

    Uma assinatura ES256 codificada em DER falha com Token signature mismatch.

    Registrado pela verificação
    fact jws-der-signature = rejected
    evidence jws-der-signature: Lcobucci\JWT\Validation\RequiredConstraintsViolated: The token violates some mandatory constraints, details: - Token signature mismatch
  • Lista de algoritmos permitidos

    required

    SignedWith fixa o signatário: um token RS256 verificado com um signatário ES256 falhou com Token signer mismatch, e assert() sem restrições lança NoConstraintsGiven.

    Registrado pela verificação
    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.
  • Tokens sem assinatura (alg none)

    rejected

    Um token sem assinatura não pode ser analisado: The JWT string is missing the Signature part.

    Registrado pela verificação
    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
  • Chave pública usada como segredo HMAC

    rejected

    Um token HS256 que usava o texto PEM como chave não passou em SignedWith para o signatário ES256 e gerou Token signer mismatch.

    Registrado pela verificação
    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
  • Curva conferida com o alg

    enforced

    O signatário ES256 recusou uma chave P-384: The length of the provided key is different than 256 bits, 384 bits provided.

    Registrado pela verificação
    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 validadas na decodificação

    no

    A análise e SignedWith não verificam o tempo: um token expirado uma hora antes passou.

    Registrado pela verificação
    fact jwt-claims-validated-on-decode = no
    evidence jwt-claims-validated-on-decode: SignedWith only, token expired an hour ago: ok
  • Tolerância padrão de diferença de relógio

    0s

    LooseValidAt e StrictValidAt não admitem nenhuma tolerância, a menos que recebam um intervalo.

    Registrado pela verificação
    fact jwt-clock-skew = 0s
  • Claim de expiração obrigatória

    no

    LooseValidAt aceitou um token sem exp; StrictValidAt exige iat, nbf e exp.

    Registrado pela verificação
    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
  • Segredo HMAC curto

    rejected

    O HS256 recusou uma chave de 5 bytes: Key provided is shorter than 256 bits, only 40 bits provided.

    Registrado pela verificação
    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

Uso

Emita um token ES256 e valide assinatura, emissor e validade estrita com 30 segundos de tolerância.
<?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";

O executor de trechos de código roda cada exemplo literalmente na imagem de contêiner php:8.5.11-cli-trixie, e a execução falha se a saída não corresponder.

Configuração
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
Comando
php usage.php
A saída deve corresponder a (expressão regular)
sub: user-42

Bibliotecas