本文へスキップ
ecdsa.com
Python

PyJWT

José Padilla による PyJWT は、Python で最も使われている JWT ライブラリです。algorithms の一覧を明示することが必須で、alg: none は決して検証を通らず、公開鍵を HMAC シークレットとして使うことは拒否されます。ただし、有効期限は任意で、時刻のずれの許容(leeway)はゼロです。
ライセンス
MIT
確認したバージョン
2.15.1 ·
ソースコード
github.com/jpadilla/pyjwt
ドキュメント
pyjwt.readthedocs.io/en/stable/

互換性の概要

JOSE と JWT

JWS アルゴリズム
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

PyJWT は jwt.encode() と jwt.decode() で JSON Web Token のエンコードとデコードを行い、PyJWKClient で JWKS エンドポイントから署名鍵を取得します。非対称アルゴリズムは crypto extra で提供され、これを指定すると pyca/cryptography がインストールされます。これを入れた 2.15.1 は、HS、RS、PS、ES の各ファミリー、ES256K、EdDSA を扱えましたが、より新しい Ed25519 と Ed448 という名前には対応しておらず、NotImplementedError: Algorithm not supported が送出されました。

検証のデフォルトは厳格です。algorithms を指定しない jwt.decode() は It is required that you pass in a value for the "algorithms" argument で失敗し、署名のない alg: none トークンは、algorithms=["none"] を指定しても検証を通りませんでした。また、PEM や OpenSSH の公開鍵を HS256 のシークレットとして使うと InvalidKeyError で拒否されました。ES256 は P-256 に結び付けられており、P-384 の鍵で署名すると曲線の不一致で失敗しました。

一方、利用者に任されている部分もあります。exp は任意なので、options 引数の require に列挙しない限り、これを含まないトークンはいつまでもデコードできます。また、leeway のデフォルトはゼロなので、時刻が数秒ずれたサーバー同士でも有効期限の判定が食い違います。短い HMAC シークレットも InsecureKeyLengthWarning が出るだけで受け入れられ、この警告はログの中で見落としがちです。シークレットは JWT シークレットチェッカーで確認してください。

ES256 署名は、JWS が求める 64 バイトの生の r‖s 形式です。署名を DER のまま組み込んだトークン(たとえば KMS の署名呼び出しの結果を使ったもの)は、汎用的な Signature verification failed で失敗します。トークンを JWT デバッガーに貼り付けて署名の長さを確認し、DER ⇄ 生形式の変換ツールで変換してください。

サンプルでは、アルゴリズム、発行者、オーディエンスを固定し、exp を必須にして、30 秒の時刻のずれを許容しています。これらが、別のサービスから受け取ったトークンに対して jwt.decode() を安全に呼び出すための 4 つの設定です。

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

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

  • JWS アルゴリズム

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

    256/384/512 ビットの HS、RS、PS、ES と、ES256K、EdDSA で署名と検証ができました。Ed25519 と Ed448 は NotImplementedError: Algorithm not supported を送出しました。

    チェックによる記録
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, ES256K, EdDSA
    evidence jws-algorithms: Ed25519: NotImplementedError: Algorithm not supported
    evidence jws-algorithms: Ed448: NotImplementedError: Algorithm not supported
  • ES256 署名のエンコーディング

    raw

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

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

    rejected

    ES256 署名を DER でエンコードしたトークンは InvalidSignatureError: Signature verification failed で失敗します。

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

    required

    algorithms を指定しない jwt.decode() は DecodeError: It is required that you pass in a value for the "algorithms" argument when calling decode(). を送出します。

    チェックによる記録
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: DecodeError: It is required that you pass in a value for the "algorithms" argument when calling decode().
  • 署名なしトークン(alg none)

    rejected

    署名のないトークンは、ES256 の鍵に対しては The specified alg value is not allowed で、algorithms=["none"] を指定しても Signature verification failed で拒否されます。読み取れるのは verify_signature: False を指定した場合だけです。

    チェックによる記録
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: with an ES256 key: InvalidAlgorithmError: The specified alg value is not allowed
    evidence jwt-alg-none: algorithms ['none'] and no key: InvalidSignatureError: Signature verification failed
    evidence jwt-alg-none: verify_signature False: returned claims
  • HMAC シークレットとして使われる公開鍵

    rejected

    PEM や OpenSSH の公開鍵を HS256 のシークレットとして使うと InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret. が送出されます。

    チェックによる記録
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: PEM public key as HS256 secret: InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.
    evidence jwt-hmac-public-key: OpenSSH public key as HS256 secret: InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.
  • alg と曲線の照合

    enforced

    P-384 の鍵で ES256 の署名を行うと The key's curve 'secp384r1' does not match the expected curve 'secp256r1' for this algorithm が送出されます。

    チェックによる記録
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: InvalidKeyError: The key's curve 'secp384r1' does not match the expected curve 'secp256r1' for this algorithm
  • デフォルトのクロックスキュー

    0s

    デフォルトでは時刻のずれを許容しません。3 秒前に期限切れになったトークンは拒否されました。

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

    no

    exp のないトークンもデコードに成功しました。必須にするには、options 引数の require に exp を列挙してください。

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

    accepted-with-warning

    5 バイトの HMAC シークレットでも署名と検証ができ、唯一の兆候は InsecureKeyLengthWarning でした。

    チェックによる記録
    fact jwt-hmac-short-secret = accepted-with-warning
    evidence jwt-hmac-short-secret: 5-byte HMAC secret: accepted; warnings: InsecureKeyLengthWarning

使い方

ES256 トークンを発行し、アルゴリズム、発行者、オーディエンスを固定し、exp を必須にして 30 秒の時刻のずれを許容したうえでデコードします。
import datetime

import jwt
from cryptography.hazmat.primitives.asymmetric import ec

key = ec.generate_private_key(ec.SECP256R1())
now = datetime.datetime.now(tz=datetime.timezone.utc)

token = jwt.encode(
    {"sub": "user-42", "iss": "https://auth.example.com", "aud": "api", "exp": now + datetime.timedelta(minutes=10)},
    key,
    algorithm="ES256",
)

# algorithms is mandatory; also require exp and allow a little clock drift.
claims = jwt.decode(
    token,
    key.public_key(),
    algorithms=["ES256"],
    audience="api",
    issuer="https://auth.example.com",
    leeway=30,
    options={"require": ["exp", "iss", "aud"]},
)
print("sub:", claims["sub"])

スニペットのテストハーネスは、すべてのサンプルをコンテナイメージ python:3.14.8-slim-trixie 内でそのまま実行し、出力が一致しなければ実行は失敗します。

セットアップ
pip install --quiet --root-user-action=ignore 'PyJWT[crypto]==2.15.1' cryptography==50.0.2
コマンド
python usage.py
一致すべき出力(正規表現)
sub: user-42

ライブラリ