本文へスキップ
ecdsa.com
PHP

firebase/php-jwt

firebase/php-jwt は、PHP で最も多くインストールされている JWT パッケージです。Key オブジェクトでアルゴリズムを固定し、短い HMAC 鍵を拒否します。ただし ES512 と PS384/512 には対応せず、発行者とオーディエンスのチェックもなく、P-384 鍵による ES256 も受け入れます。
ライセンス
BSD-3-Clause
確認したバージョン
7.2.1 ·
ソースコード
github.com/firebase/php-jwt
ドキュメント
github.com/firebase/php-jwt#readme

互換性の概要

JOSE と JWT

JWS アルゴリズム
HS256HS384HS512RS256RS384RS512ES256ES384ES256KEdDSA

php-jwt は、openssl 拡張モジュールと libsodium を基盤に、JWT::encode() と JWT::decode() でトークンをエンコード・デコードします。バージョン 6 からは Key オブジェクト(鍵データとアルゴリズムの組)が必須になったため、アルゴリズムはトークンではなくアプリケーションのコードで決まります。RS256 トークンと、EC 鍵の PEM テキストで偽造した HS256 トークンは、どちらも new Key($pem, 'ES256') に対して Incorrect key for this algorithm で失敗し、署名のないトークンは Algorithm not supported で失敗します。ただし、この保護が有効なのはアプリケーション側が Key を構築している場合だけです。new Key($pem, 'HS256') では偽造したトークンが検証を通りました。

バージョン 7.2.1 は HS256/384/512、RS256/384/512、ES256、ES384、ES256K、EdDSA に対応しています。ES512 には対応しておらず(Algorithm not supported)、PS256 には phpseclib/phpseclib のインストールが必要で、PS384 と PS512 はまったく使えません。これらのアルゴリズムを使う発行者のトークンには、web-token か lcobucci/jwt が必要です。ハッシュより短い HMAC 鍵は拒否されます(Provided key is too short)。

ES256 は P-256 に限定されていません。ES256 ヘッダーのまま P-384 鍵で署名したトークンを、このライブラリ自身が生成し、受け入れました。他のライブラリはこれを拒否します。ES256 署名は JWS が求める 64 バイトの生の r‖s に変換され、DER 署名は Signature verification failed で失敗します。トークンの署名は JWT デバッガーで確認してください。

クレームの処理は最小限です。exp、nbf、iat は存在する場合にだけチェックされ、JWT::$leeway はデフォルト値が 0 のグローバルな静的プロパティで、発行者とオーディエンスの検証はまったく行われません。そのためサンプルでは exp と iss を自前でチェックしています。JWKS による検証では、JWK::parseKeySet() が鍵セットを Key オブジェクトに変換します。プロバイダーが公開する鍵セットは JWKS チェッカーで確認してください。

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

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

  • JWS アルゴリズム

    HS256HS384HS512RS256RS384RS512ES256ES384ES256KEdDSA

    HS256/384/512、RS256/384/512、ES256、ES384、ES256K、EdDSA で署名と検証ができました。ES512 と PS384/512 には対応しておらず、PS256 には phpseclib が必要です。

    チェックによる記録
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES256K, EdDSA
    evidence jws-algorithms: PS256: DomainException: phpseclib/phpseclib is required for PS256 support
    evidence jws-algorithms: PS384: DomainException: Algorithm not supported
    evidence jws-algorithms: PS512: DomainException: Algorithm not supported
    evidence jws-algorithms: ES512: DomainException: Algorithm not supported
    evidence jws-algorithms: Ed25519: no key family
  • ES256 署名のエンコーディング

    raw

    ES256 署名は 64 バイトの生の r‖s です。

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

    rejected

    DER エンコードの ES256 署名は SignatureInvalidException: Signature verification failed で失敗します。

    チェックによる記録
    fact jws-der-signature = rejected
    evidence jws-der-signature: Firebase\JWT\SignatureInvalidException: Signature verification failed
  • アルゴリズムの許可リスト

    required

    decode() にはアルゴリズムを指定した Key オブジェクトが必要です。鍵の文字列だけを渡すと失敗し、RS256 トークンを new Key($ecPem, 'ES256') で検証すると Incorrect key for this algorithm で失敗します。

    チェックによる記録
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: RS256 token against new Key(EC PEM, 'ES256'): UnexpectedValueException: Incorrect key for this algorithm
    evidence jwt-algorithm-allowlist: decode() with a bare key string: UnexpectedValueException: "kid" empty, unable to lookup correct key
  • 署名なしトークン(alg none)

    rejected

    署名のないトークンは Algorithm not supported で失敗します。

    チェックによる記録
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: UnexpectedValueException: Algorithm not supported
  • HMAC シークレットとして使われる公開鍵

    rejected

    PEM テキストを鍵とした HS256 トークンは、ES256 の Key に対しては失敗しましたが、new Key($pem, 'HS256') では検証を通りました。

    チェックによる記録
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: Key(PEM, 'ES256'): UnexpectedValueException: Incorrect key for this algorithm
    evidence jwt-hmac-public-key: Key(PEM, 'HS256') built by the application: ok
  • alg と曲線の照合

    not-enforced

    P-384 鍵を使って ES256 として署名したトークンが生成され、検証にも成功しました。

    チェックによる記録
    fact jwt-ecdsa-curve-check = not-enforced
    evidence jwt-ecdsa-curve-check: ok
  • デフォルトのクロックスキュー

    0s

    JWT::$leeway は 0 です。3 秒前に期限切れになったトークンは拒否されました。

    チェックによる記録
    fact jwt-clock-skew = 0s
    evidence jwt-clock-skew: JWT::$leeway = 0
  • 有効期限クレームの要否

    no

    exp のないトークンもデコードできました。

    チェックによる記録
    fact jwt-exp-required = no
  • 短い HMAC シークレット

    rejected

    5 バイトの HS256 鍵は Provided key is too short で失敗します。

    チェックによる記録
    fact jwt-hmac-short-secret = rejected
    evidence jwt-hmac-short-secret: 5-byte HS256 key: DomainException: Provided key is too short

使い方

ES256 トークンを発行し、アルゴリズムを固定した Key で、30 秒の時刻のずれを許容してデコードし、exp と iss を明示的にチェックします。
<?php
require 'vendor/autoload.php';

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$private = openssl_pkey_new(['private_key_type' => OPENSSL_KEYTYPE_EC, 'curve_name' => 'prime256v1']);
$publicPem = openssl_pkey_get_details($private)['key'];

$token = JWT::encode(['sub' => 'user-42', 'iss' => 'https://auth.example.com', 'exp' => time() + 600], $private, 'ES256');

// The Key object pins the algorithm; leeway is a static setting, 0 by default.
JWT::$leeway = 30;
$claims = JWT::decode($token, new Key($publicPem, 'ES256'));

// php-jwt checks exp/nbf/iat only when present and never checks iss or aud: do it here.
if (!isset($claims->exp) || $claims->iss !== 'https://auth.example.com') {
    throw new UnexpectedValueException('unexpected claims');
}
echo '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 firebase/php-jwt:7.2.1
コマンド
php usage.php
一致すべき出力(正規表現)
sub: user-42

ライブラリ