本文へスキップ
ecdsa.com
PHP

lcobucci/jwt

Luís Cobucci による lcobucci/jwt は、Laravel Passport や多くの PHP フレームワークを支える JWT ライブラリです。すべてのチェックは明示的な制約として指定し、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 トークンも、EC 鍵の PEM テキストで偽造した HS256 トークンも、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 ビットの鍵を拒否しました。そのため、不正な形式のトークンがそもそも生成されません。

時刻のチェックも制約なので、列挙したときにしか実行されません。1 時間前に期限切れになったトークンも、SignedWith だけなら通りました。LooseValidAt は exp、nbf、iat があればチェックし、exp のないトークンを受け入れました。StrictValidAt は 3 つすべてを必須とし、iat のないトークンは "Issued At" claim missing で、iat と exp はあっても nbf のないトークンは "Not Before" claim missing で失敗しました。どちらも時刻のずれを許容する間隔(leeway)を任意で受け取り、デフォルトはゼロです。サンプルでは 3 つのクレームをすべて設定し、1 回の呼び出しで署名、発行者、厳格な有効性をまとめて検証します。

バージョン 5.6.1 は 256/384/512 の HS、RS、ES と EdDSA に対応しています。PS* の署名器と ES256K はないため、RSA-PSS を使う発行者のトークンには web-token が必要です。ES256 署名は 64 バイトの生の r‖s で、DER の署名は Token signature mismatch で失敗します。トークンがどちらのエンコーディングかは JWT デバッガーで確認できます。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/lcobucci-jwt/run.sh がコンテナイメージ php:8.5.11-cli-trixie 内で lcobucci/jwt 5.6.1 を実行しました。このページの内容はその出力と一致していなければならず、各事実には該当する出力行を示しています。

  • JWS アルゴリズム

    HS256HS384HS512RS256RS384RS512ES256ES384ES512EdDSA

    256/384/512 の HS、RS、ES と 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 バイトの生の r‖s です。

    チェックによる記録
    fact jws-ecdsa-encoding = raw
  • JWS 内の DER 署名

    rejected

    DER でエンコードされた ES256 署名は 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 は署名器を固定します。ES256 の署名器に対する RS256 トークンは 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

    PEM テキストを鍵とした HS256 トークンは、ES256 の署名器を指定した SignedWith で 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 は時刻をチェックしません。1 時間前に期限切れになったトークンも通りました。

    チェックによる記録
    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

ライブラリ