Python JWT デコード PyJWT 方法

·DevOps Engineer & Python Automation Specialist·レビュー担当Maria Santos·公開日

無料の JWTデコーダー をブラウザで直接使用 — インストール不要。

JWTデコーダー をオンラインで試す →

トークンベース認証を使う API は、どこかの時点で必ず JWT を渡してきます。 開発中にその中身を確認する作業は日常的に発生します。Python の JWT デコーダーは 不透明な base64 文字列を、実際に扱える読みやすいクレームの辞書に変換します。PyPI で入手できるパッケージは PyJWT — インストールは pip install PyJWT ですが、インポート名は import jwt です。このガイドでは、署名検証付きの jwt.decode()、 簡易確認のための署名なしデコード、 ライブラリ不要の手動 base64 デコード、RS256 公開鍵検証、そして本番の認証システムで実際に遭遇したよくある落とし穴を解説します。 コードを書かずにさっと確認したい場合は、オンライン JWT デコーダーを使うと即座に結果が得られます。 すべてのサンプルは Python 3.10+ と PyJWT 2.x を対象としています。

  • pip install PyJWT でインストールし、import jwt でインポートします。パッケージ名とインポート名が異なる点でほぼ全員が一度はつまずきます。
  • jwt.decode(token, key, algorithms=["HS256"]) はクレームを含む通常の dict を返します。algorithms は必ず明示的に渡してください。
  • 署名を検証せずにクレームを確認するには:jwt.decode(token, options={"verify_signature": False}, algorithms=["HS256"])。
  • RSA/EC トークンには:pip install PyJWT cryptography — 非対称アルゴリズムには cryptography バックエンドが必要です。
  • 手動デコード(base64 + json)はライブラリ不要で動作しますが、署名および有効期限の検証は一切行いません。

JWT デコードとは何か

JSON Web Token は、ドットで区切られた 3 つの base64url エンコード済みセグメントで構成されています。 ヘッダー(アルゴリズムとトークン種別)、ペイロード(クレーム — ユーザー ID、ロール、有効期限)、 そして署名です。JWT をデコードするとは、ヘッダーとペイロードのセグメントを取り出し、 base64url デコードして得られた JSON をクレームの辞書に変換することを意味します。

ヘッダーにはトークンの署名に使われたアルゴリズムが記述されており、適切な検証鍵を特定するための kid (鍵 ID)が含まれる場合もあります。ペイロードには実際のデータが格納されています。 トークンの発行対象(sub)、 有効期限(exp)、 対象サービス(aud)、 そしてアプリケーション固有のカスタムクレームです。 署名セグメントはトークンが改ざんされていないことを証明しますが、検証には秘密鍵または公開鍵が必要です。 デコードと検証は独立した操作です。署名を検証せずにペイロードをデコードすること(デバッグに有用)は可能ですが、 未検証のクレームを認可判断に使用してはなりません。

Before · json
After · json
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
  "sub": "usr_8f2a",
  "role": "admin",
  "exp": 1711815600
}

jwt.decode() — PyJWT でデコードと検証を行う

jwt.decode() は PyJWT ライブラリの主要な関数です。エンコード済みトークン文字列、秘密鍵(HMAC アルゴリズムの場合) または公開鍵(RSA/EC の場合)、そして必須の algorithms リストを受け取ります。署名を検証し、 exp nbf などの標準クレームをチェックして、ペイロードを Python の辞書として返します。 署名の不正、有効期限切れ、アルゴリズム不一致など、何らかの問題が発生した場合は特定の例外が発生します。

最小限の動作サンプル

Python 3.10+
import jwt

# 発行者とこのサービス間の共有シークレット
SECRET_KEY = "k8s-webhook-signing-secret-2026"

# まずトークンをエンコード(認証サーバーが発行する処理をシミュレート)
token = jwt.encode(
    {"sub": "usr_8f2a", "role": "admin", "team": "platform"},
    SECRET_KEY,
    algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3Jf...

# トークンをデコードして検証
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin', 'team': 'platform'}
print(payload["role"])
# admin

algorithms パラメータは単一の文字列ではなくリストであり、PyJWT 2.x では必須です。 これはセキュリティ上の設計です。省略すると、攻撃者がヘッダーに alg: none を設定したトークンを作成して検証を完全に回避できてしまいます。 アプリケーションが受け付けるアルゴリズムを必ず明示的に指定してください。 HS256 トークンのみを発行する場合、リストは ["HS256"] とすべきです。 ["HS256", "RS256", "none"] のように広げると攻撃対象が拡大します。

最初に混乱した点を一つ挙げると:PyJWT 2.x では jwt.encode() がバイト列ではなく文字列を返すよう変更されました。エンコードされたトークンに対して .decode("utf-8") を呼び出している古い Stack Overflow の回答は PyJWT 1.x 時代のコードであり、 バージョン 2.x では AttributeError が発生します。トークンはすでに文字列なので、そのまま使用してください。

有効期限付きの完全なラウンドトリップ

Python 3.10+ — encode then decode with exp
import jwt
from datetime import datetime, timedelta, timezone

SECRET_KEY = "webhook-processor-secret"

# 1 時間後に期限切れとなるトークンを作成
payload = {
    "sub": "svc_payment_processor",
    "iss": "auth.internal.example.com",
    "aud": "https://api.example.com",
    "exp": datetime.now(timezone.utc) + timedelta(hours=1),
    "permissions": ["orders:read", "refunds:create"],
}

token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")

# 後でリクエストヘッダーにトークンが届いた場合:
try:
    decoded = jwt.decode(
        token,
        SECRET_KEY,
        algorithms=["HS256"],
        audience="https://api.example.com",
        issuer="auth.internal.example.com",
    )
    print(f"Service: {decoded['sub']}")
    print(f"Permissions: {decoded['permissions']}")
except jwt.ExpiredSignatureError:
    print("Token expired — request re-authentication")
except jwt.InvalidAudienceError:
    print("Token not intended for this API")
except jwt.InvalidIssuerError:
    print("Token issued by unknown authority")
注意:PyJWT はエンコード時に datetime オブジェクトを自動的に Unix タイムスタンプに変換します。 デコード時には exp iat nbf クレームは datetime オブジェクトではなく整数として返されます。 必要に応じて datetime.fromtimestamp(payload["exp"], tz=timezone.utc) で変換してください。

署名検証なしで JWT をデコードする

トークンを検証する前にクレームを読み取る必要がある場合があります。 よくあるシナリオとして、トークンヘッダーに kid (鍵 ID)フィールドが含まれており、検証前に JWKS エンドポイントから対応する公開鍵を取得する必要がある場合があります。 PyJWT は verify_signature: False オプションでこれをサポートします。 algorithms リストは引き続き渡しますが、 key 引数は無視されます。

Python 3.10+ — unverified decode
import jwt

token = (
    "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZy0xNzI2In0"
    ".eyJzdWIiOiJ1c3JfM2M3ZiIsInNjb3BlIjoicmVhZDpvcmRlcnMiLCJpc3MiOiJhdXRoLmV4YW1wbGUuY29tIn0"
    ".signature_placeholder"
)

# ステップ 1:ルーティング情報を取得するため検証なしでクレームを読み取る
unverified = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["RS256"]
)
print(unverified)
# {'sub': 'usr_3c7f', 'scope': 'read:orders', 'iss': 'auth.example.com'}

# ステップ 2:使用する鍵を特定するためヘッダーを読み取る
header = jwt.get_unverified_header(token)
print(header)
# {'alg': 'RS256', 'typ': 'JWT', 'kid': 'sig-1726'}
# header['kid'] を使って JWKS エンドポイントから正しい公開鍵を取得する
警告:未検証のトークンは信頼できません。このパターンはルーティング判断(取得する鍵の特定、テナントの特定)にのみ使用してください。 未検証のクレームに基づいて認可判断を行ってはなりません。攻撃者はペイロードに任意の内容を埋め込むことができます。

ここには微妙な違いがあります。 jwt.get_unverified_header() はヘッダー(最初のセグメント)のみを読み取ります。 verify_signature: False を指定した jwt.decode() 呼び出しはペイロード(2 番目のセグメント)を読み取ります。 この 2 つを組み合わせることで、鍵なしでトークンからすべての情報を取り出せます。 PyJWT は署名検証が無効な場合でも、トークンが正しい構造(ドットで区切られた 3 つのセグメント、有効な base64、有効な JSON) であることを検証します。トークンの構造が不正な場合、指定したオプションに関わらず DecodeError が発生します。

jwt.decode() パラメータリファレンス

完全なシグネチャは jwt.decode(jwt, key, algorithms, options, audience, issuer, leeway, require) です。 algorithms 以降のパラメータはすべてキーワード引数のみです。

パラメータ
デフォルト
説明
jwt
str | bytes
(必須)
デコード対象のエンコード済み JWT 文字列
key
str | bytes | dict
(必須)
検証用の秘密鍵(HMAC)または公開鍵(RSA/EC)
algorithms
list[str]
(必須)
許可するアルゴリズムのリスト(例:["HS256"]、["RS256"])。省略不可。
options
dict
{}
検証フラグの上書き:verify_signature、verify_exp、verify_aud など
audience
str | list[str]
None
期待する aud クレーム — 不一致の場合 InvalidAudienceError を発生
issuer
str
None
期待する iss クレーム — 不一致の場合 InvalidIssuerError を発生
leeway
timedelta | int
0
exp および nbf 検証における時計のずれ許容秒数
require
list[str]
[]
必須クレームのリスト — 存在しない場合 MissingRequiredClaimError を発生

options 辞書は PyJWT が実行する検証を細かく制御します。各キーが個別のチェックに対応しています: verify_signature verify_exp verify_nbf verify_iss verify_aud、 そして verify_iat です。明示的に False に設定しない限り、すべてデフォルトで True です。本番環境ではデフォルト設定のままにしてください。個別のチェックを無効にするのは、 古いテストトークンを使用して有効期限を一時的に無視する必要がある開発中のみです。

Python 3.10+ — using options and require
import jwt

# 特定のクレームの存在を必須にする — 不在の場合 MissingRequiredClaimError が発生
payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"],
    options={"require": ["exp", "iss", "sub"]},
    issuer="auth.internal.example.com",
)

# 開発時のみ:古いトークンでテストするため有効期限の検証をスキップ
dev_payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"],
    options={"verify_exp": False},  # 本番環境では使用しないこと
)

base64 と json を使った手動 JWT デコード

Python 標準ライブラリだけを使って JWT ペイロードをデコードできます。 pip install は不要です。これが実際に役立つ場面がいくつかあります。依存関係の追加が大げさなデバッグスクリプト、 標準ライブラリしか使えない制限された CI 環境、コールドスタート時間を最小化したい AWS Lambda 関数、 あるいは JWT が内部的にどのような仕組みで動作しているかを理解したい場合などです。 手順はシンプルです。ドットで分割し、対象のセグメントを取り出し、base64 パディングを追加してデコードし、 JSON をパースします。

base64 モジュールと json モジュールはどちらも Python 標準ライブラリに含まれているため、Python 3.6 以降の環境であればそのまま動作します。 以下の関数はヘッダー(最初のセグメント)とペイロード(2 番目のセグメント)をそれぞれ処理します。

Python 3.10+ — manual JWT decode without any library
import base64
import json

def decode_jwt_payload(token: str) -> dict:
    """Decode the JWT payload without signature verification.
    Works with any JWT — HS256, RS256, ES256, etc.
    """
    parts = token.split(".")
    if len(parts) != 3:
        raise ValueError(f"Expected 3 JWT segments, got {len(parts)}")

    payload_b64 = parts[1]
    # base64url uses - and _ instead of + and /
    # Python's urlsafe_b64decode handles this, but needs padding
    payload_b64 += "=" * (-len(payload_b64) % 4)
    payload_bytes = base64.urlsafe_b64decode(payload_b64)
    return json.loads(payload_bytes)


def decode_jwt_header(token: str) -> dict:
    """Decode the JWT header (algorithm, key ID, type)."""
    header_b64 = token.split(".")[0]
    header_b64 += "=" * (-len(header_b64) % 4)
    return json.loads(base64.urlsafe_b64decode(header_b64))


# Example usage
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

header = decode_jwt_header(token)
print(f"Algorithm: {header['alg']}")
# Algorithm: HS256

claims = decode_jwt_payload(token)
print(f"Subject: {claims['sub']}")
print(f"Role: {claims['role']}")
# Subject: usr_8f2a
# Role: admin

パディングの処理(+= "=" * (-len(s) % 4))は 誰もが忘れがちな部分です。JWT の base64url エンコードは末尾の = 文字を省略しますが、Python の urlsafe_b64decode はこれを必要とします。パディングの修正を忘れると binascii.Error: Incorrect padding が発生します。

注意:手動デコードでは署名を検証しません。誰でも JWT のペイロードを変更して再エンコードできます。 このアプローチは確認とデバッグのみに使用し、認可ロジックには絶対に使用しないでください。 重要な処理には必ず実際の鍵を使って jwt.decode() を使用してください。

API レスポンスとトークンファイルから JWT をデコードする

実際によくある 2 つのシナリオとして、HTTP レスポンス(OAuth トークンエンドポイント、ログイン API)からの JWT 抽出と、 ファイル(サービスアカウントの認証情報、Kubernetes マウント済みシークレット、ディスクにキャッシュされたトークン)からのトークン読み取りがあります。 どちらも適切なエラー処理が必要です。ネットワークリクエストは失敗します。ファイルが消えることもあります。 キャッシュ時と読み取り時の間にトークンが期限切れになることもあります。

以下のサンプルは HTTP 通信に httpx を使用します( requests に置き換えても同じパターンです)。ファイル操作には pathlib.Path を使います。各サンプルでは汎用の except Exception ではなく特定の PyJWT 例外を捕捉しており、失敗モードに応じた適切な対応(有効期限切れの場合は再認証、署名エラーの場合はアラート、ネットワークタイムアウトの場合はリトライ)が可能です。

API レスポンスから JWT をデコードする

Python 3.10+ — decode JWT from OAuth token endpoint
import jwt
import httpx  # or requests

TOKEN_ENDPOINT = "https://auth.example.com/oauth/token"
SECRET_KEY = "shared-webhook-signing-key"

def get_and_decode_token() -> dict:
    """Fetch an access token from the auth server and decode it."""
    try:
        response = httpx.post(
            TOKEN_ENDPOINT,
            data={
                "grant_type": "client_credentials",
                "client_id": "svc_order_processor",
                "client_secret": "cs_9f3a7b2e",
            },
            timeout=10.0,
        )
        response.raise_for_status()
    except httpx.HTTPError as exc:
        raise RuntimeError(f"Token request failed: {exc}") from exc

    token_data = response.json()
    access_token = token_data["access_token"]

    try:
        payload = jwt.decode(
            access_token,
            SECRET_KEY,
            algorithms=["HS256"],
            audience="https://api.example.com",
        )
        return payload
    except jwt.InvalidTokenError as exc:
        raise RuntimeError(f"Invalid token from auth server: {exc}") from exc


claims = get_and_decode_token()
print(f"Service: {claims['sub']}, Scopes: {claims.get('scope', 'none')}")

ファイルから JWT をデコードする

Python 3.10+ — read and decode a cached token file
import jwt
from pathlib import Path
from datetime import datetime, timezone

TOKEN_PATH = Path("/var/run/secrets/service-account-token")
PUBLIC_KEY_PATH = Path("/etc/ssl/auth/public_key.pem")

def decode_token_from_file() -> dict:
    """Read a JWT from a file, verify with a PEM public key."""
    try:
        token = TOKEN_PATH.read_text().strip()
        public_key = PUBLIC_KEY_PATH.read_text()
    except FileNotFoundError as exc:
        raise RuntimeError(f"Missing file: {exc.filename}") from exc

    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=["RS256"],
            audience="https://internal-api.example.com",
        )
    except jwt.ExpiredSignatureError:
        exp_time = jwt.decode(
            token,
            options={"verify_signature": False},
            algorithms=["RS256"],
        ).get("exp", 0)
        expired_at = datetime.fromtimestamp(exp_time, tz=timezone.utc)
        raise RuntimeError(f"Token expired at {expired_at.isoformat()}")
    except jwt.InvalidTokenError as exc:
        raise RuntimeError(f"Token verification failed: {exc}") from exc

    return payload


claims = decode_token_from_file()
print(f"Subject: {claims['sub']}, Issuer: {claims['iss']}")

コマンドラインでの JWT デコード

スクリプトを書かずにターミナルからトークンの中身を確認したい場合があります。 OAuth フローのデバッグ中に Authorization ヘッダーの中身を確認したいとき、あるいはブラウザの DevTools から取得したトークンの有効期限を確認したいときなどです。 Python の -c フラグを使えばワンライナーで実現できます。トークンをパイプで渡すと、クレームが整形された JSON として出力されます。 スクリプトファイルも仮想環境も不要です。

Bash
# Decode JWT payload from clipboard or variable (no verification)
echo "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.sig" \
  | python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
payload = token.split('.')[1]
payload += '=' * (-len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
"
# {
#   "sub": "usr_8f2a",
#   "role": "admin"
# }
Bash
# Decode JWT header to check algorithm and key ID
echo "eyJhbGciOiJSUzI1NiIsImtpZCI6InNpZy0xNzI2In0.payload.sig" \
  | python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
header = token.split('.')[0]
header += '=' * (-len(header) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(header)), indent=2))
"
# {
#   "alg": "RS256",
#   "kid": "sig-1726"
# }
Bash
# If PyJWT is installed, verify and decode in one step
python3 -c "
import jwt, sys, json
token = sys.argv[1]
payload = jwt.decode(token, options={'verify_signature': False}, algorithms=['HS256'])
print(json.dumps(payload, indent=2))
" "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSJ9.sig"

ターミナルを使わずに視覚的に確認したい場合は、トークンをToolDeck JWT デコーダーに貼り付けると、 ヘッダー、ペイロード、署名の検証ステータスを即座に確認できます。

python-jose とその他の代替ライブラリ

python-jose は JWS、JWE(暗号化トークン)、JWK をネイティブにサポートする代替 JWT ライブラリです。 ペイロード自体が暗号化されている JWE トークンを扱う必要がある場合、PyJWT は JWE を一切サポートしていないため python-jose が適切な選択肢です。 このライブラリには JWKS 鍵セットの処理機能が組み込まれており、Auth0、Okta、Keycloak などの ローテーティング鍵セットを公開するアイデンティティプロバイダーとの統合が容易になります。 デコードのインターフェースは PyJWT とほぼ同じなので、切り替えに必要なコード変更は最小限です。

Python 3.10+ — python-jose
# pip install python-jose[cryptography]
from jose import jwt as jose_jwt

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInNjb3BlIjoib3JkZXJzOnJlYWQifQ.signature"

# 検証付きデコード — PyJWT と同じパターン
payload = jose_jwt.decode(
    token,
    "signing-secret-key",
    algorithms=["HS256"],
    audience="https://api.example.com",
)
print(payload)
# {'sub': 'usr_8f2a', 'scope': 'orders:read'}

# 未検証デコード
claims = jose_jwt.get_unverified_claims(token)
header = jose_jwt.get_unverified_header(token)
print(f"Algorithm: {header['alg']}, Subject: {claims['sub']}")

推奨としては、まず PyJWT から始めることをお勧めします。JWT のユースケースの 95% をカバーし、 コミュニティが最大で、API も整理されています。JWE サポートが必要な場合や JWKS 処理が有利な場合は python-jose に切り替えてください。 もう一つの選択肢として Authlib があります。これはより大きな OAuth/OIDC フレームワークの中に JWT 処理が含まれています。 OAuth クライアントフローにすでに Authlib を使用している場合は、 authlib.jose.jwt モジュールを使うことで 2 つ目の JWT 依存関係を追加せずに済みます。 それ以外の場合、トークンのデコードだけのために追加するには重すぎる依存関係です。

ターミナル出力にシンタックスハイライトを追加する

ターミナルで JWT のクレームをそのまま表示するのは簡易確認には十分ですが、 トークンペイロードを日常的にデバッグしている場合(社内認証ゲートウェイ構築中には毎日この作業をしていました)、 カラー出力は大きな違いをもたらします。文字列値、数値、真偽値、null がそれぞれ異なる色で表示されるため、 すべての文字を読まなくても欠けているパーミッションや誤った有効期限タイムスタンプを一目で発見できます。

rich ライブラリ(pip install rich)には JSON 文字列または Python の dict を受け取り、ターミナルにシンタックスハイライト付きで出力する print_json 関数があります。PyJWT と組み合わせると、2 行で JWT の確認ワークフローが完成します。

Python 3.10+ — rich terminal output
# pip install rich PyJWT
import jwt
from rich import print_json

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsInBlcm1pc3Npb25zIjpbIm9yZGVyczpyZWFkIiwicmVmdW5kczpjcmVhdGUiXSwiZXhwIjoxNzExODE1NjAwfQ.sig"

payload = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["HS256"]
)

# ターミナルにカラー・インデント付きで JSON を出力
print_json(data=payload)
# {
#   "sub": "usr_8f2a",           ← 文字列は緑色
#   "role": "admin",
#   "permissions": [
#     "orders:read",
#     "refunds:create"
#   ],
#   "exp": 1711815600            ← 数値はシアン色
# }
注意:rich の出力には ANSI エスケープコードが含まれています。 ファイルへの書き込みや API エンドポイントからの返却には使用しないでください — ターミナル表示専用です。 プレーンテキスト出力が必要な場合は json.dumps() を使用してください。

大量トークンのバッチ処理

JWT トークン自体のサイズは小さい(通常 2 KB 未満)ですが、まとめて処理する場面があります。 セキュリティインシデント後の監査ログ分析。認証プロバイダー切り替え時のセッション移行スクリプト。 過去 90 日間に発行されたすべてのトークンが正しい鍵で署名されていることを証明するコンプライアンスのバッチ検証。 数万件のトークンが NDJSON ログファイルに記録されている場合、1 行ずつ処理することでファイル全体をメモリに読み込まずに済み、 結果をインクリメンタルに報告できます。

監査ログからトークンをバッチ検証する

Python 3.10+ — streaming token validation
import jwt
import json
from pathlib import Path

SECRET_KEY = "audit-log-signing-key"

def validate_token_log(log_path: str) -> dict:
    """Process an NDJSON file where each line has a 'token' field.
    Returns counts of valid, expired, and invalid tokens.
    """
    stats = {"valid": 0, "expired": 0, "invalid": 0}

    with open(log_path) as fh:
        for line_num, line in enumerate(fh, 1):
            line = line.strip()
            if not line:
                continue

            try:
                record = json.loads(line)
                token = record["token"]
            except (json.JSONDecodeError, KeyError):
                stats["invalid"] += 1
                continue

            try:
                jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
                stats["valid"] += 1
            except jwt.ExpiredSignatureError:
                stats["expired"] += 1
            except jwt.InvalidTokenError:
                stats["invalid"] += 1

    return stats


result = validate_token_log("auth-events-2026-03.ndjson")
print(f"Valid: {result['valid']}, Expired: {result['expired']}, Invalid: {result['invalid']}")
# Valid: 14832, Expired: 291, Invalid: 17

NDJSON トークンエクスポートからクレームを抽出する

Python 3.10+ — extract and transform claims from token log
import base64
import json
from datetime import datetime, timezone

def extract_claims_stream(input_path: str, output_path: str):
    """Read tokens line by line, decode payloads, write flattened claims."""
    with open(input_path) as infile, open(output_path, "w") as outfile:
        for line in infile:
            line = line.strip()
            if not line:
                continue

            record = json.loads(line)
            token = record.get("access_token", "")
            parts = token.split(".")
            if len(parts) != 3:
                continue

            payload_b64 = parts[1] + "=" * (-len(parts[1]) % 4)
            claims = json.loads(base64.urlsafe_b64decode(payload_b64))

            # Flatten into an audit-friendly record
            flat = {
                "timestamp": record.get("timestamp"),
                "subject": claims.get("sub"),
                "issuer": claims.get("iss"),
                "expired_at": datetime.fromtimestamp(
                    claims.get("exp", 0), tz=timezone.utc
                ).isoformat(),
            }
            outfile.write(json.dumps(flat) + "\n")

extract_claims_stream("token-audit.ndjson", "claims-extract.ndjson")
注意:数百 MB 未満のファイルには 1 行ずつの読み取りで十分に効率的です。 非常に大きなトークンダンプでパフォーマンスの限界に達した場合は、 各トークンが独立しているため multiprocessing.Pool を使って検証を複数コアに分散させることを検討してください。

よくある間違い

以下の 4 つの間違いはコードレビューや Stack Overflow の質問で繰り返し登場します。 どれも犯しやすく、PyJWT のエラーメッセージが必ずしも原因を直接示してくれるとは限りません。 パッケージ名の混乱だけで、誰かが誤ったライブラリをインストールして全く想定外の API に遭遇し、 何時間もデバッグに費やすことがあります。

PyJWT と python-jwt のパッケージ名を混同する

問題: pip install jwt または pip install python-jwt を実行すると、まったく別のパッケージがインストールされます。その後 import jwt をすると失敗するか、想定と異なる API が得られます。

修正: 必ず pip install PyJWT でインストールしてください。インポートは import jwt です。pip show PyJWT で正しいパッケージが入っているか確認できます。

Before · Python
After · Python
pip install jwt
# or
pip install python-jwt

import jwt  # wrong package — different API entirely
pip install PyJWT

import jwt  # correct — this is PyJWT
print(jwt.__version__)  # 2.x
algorithms パラメータを省略する

問題: PyJWT 1.x では algorithms は省略可能で、任意のアルゴリズムが許可されていました。これにより攻撃者が alg: none を設定できるセキュリティ上の脆弱性が生じていました。PyJWT 2.x では algorithms が省略されると DecodeError が発生します。

修正: algorithms は必ず明示的なリストとして渡してください。アプリケーションが実際にトークンを発行しているアルゴリズムのみを使用してください。

Before · Python
After · Python
# PyJWT 2.x — this raises DecodeError
payload = jwt.decode(token, SECRET_KEY)
# DecodeError: algorithms must be specified
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
RS256 に対して jwt.decode() に誤った鍵型を渡す

問題: algorithms=["RS256"] と共に文字列のシークレットを jwt.decode() に渡すと InvalidSignatureError が発生します。RS256 には共有シークレット文字列ではなく PEM エンコードされた公開鍵が必要です。

修正: ファイルまたは環境変数から PEM 公開鍵を読み込んでください。cryptography パッケージをインストールしてください:pip install PyJWT cryptography。

Before · Python
After · Python
# This fails — RS256 needs a public key, not a string secret
payload = jwt.decode(token, "my-secret", algorithms=["RS256"])
# InvalidSignatureError
public_key = open("public_key.pem").read()
payload = jwt.decode(token, public_key, algorithms=["RS256"])
手動デコード時に base64 パディングを忘れる

問題: JWT の base64url エンコードは末尾の = 文字を省略します。パディングを修正せずにそのままセグメントを渡すと、Python の base64.urlsafe_b64decode は binascii.Error: Incorrect padding を発生させます。

修正: デコード前にパディングを追加してください:segment += '=' * (-len(segment) % 4)。この計算式は常に正しい数のパディング文字(0、1、2、または 3 個)を生成します。

Before · Python
After · Python
import base64
payload_b64 = token.split(".")[1]
data = base64.urlsafe_b64decode(payload_b64)
# binascii.Error: Incorrect padding
import base64
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)
data = base64.urlsafe_b64decode(payload_b64)  # works

PyJWT と代替手段の比較

方法
署名検証
クレーム検証
カスタム型
インストール
PyJWT jwt.decode()
✓ (exp, aud, iss, nbf)
N/A(dict を返す)
pip install PyJWT
PyJWT 未検証デコード
N/A
pip install PyJWT
手動 base64 デコード
N/A
不要(標準ライブラリ)
python-jose jwt.decode()
N/A
pip install python-jose
Authlib jwt.decode()
N/A
pip install Authlib
PyJWT + cryptography
✓ (RSA/EC)
N/A
pip install PyJWT cryptography

ほとんどの Python アプリケーションでは PyJWT が最適な出発点です。 HMAC と(cryptography バックエンドを使えば)RSA および EC 署名検証をカバーします。 JWE(暗号化トークン)が必要な場合は python-jose または Authlib に切り替えてください。 手動の base64 デコードはデバッグには使えますが、安全性の保証は一切ありません。

各オプションの使い分けについて:標準的な HS256 または RS256 検証を行う Web サービスには PyJWT。 暗号化トークンや JWKS ローテーションが必要な場合は python-jose。 pip が使えない環境(CI コンテナ、制限された本番ホスト、依存関係を最小化したい AWS Lambda のコールドスタートなど)での 簡易確認には手動 base64 デコード。OAuth クライアントフローにすでに Authlib を使用していて 別の JWT ライブラリが冗長になる場合は Authlib。

コードなしの代替手段として、JWT デコーダーに トークンを貼り付けると、デコードされたヘッダーとペイロードをクレーム検証フィードバック付きで確認できます。

よくある質問

Python で署名を検証せずに JWT をデコードするにはどうすればよいですか?

jwt.decode() に options={"verify_signature": False} と algorithms=["HS256"] を渡します。署名を検証せずにペイロードの dict を返します。適切な公開鍵を取得する前にクレームを読み取る場合や、開発中のデバッグにのみ使用してください。アクセス制御に関わるトークンでは検証を省略しないでください。

Python
import jwt

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

payload = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["HS256"]
)
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin'}

PyJWT と python-jwt の違いは何ですか?

PyJWT(pip install PyJWT、import jwt)は月間ダウンロード数 8,000 万件を超える Python 向け最も人気の JWT ライブラリです。python-jwt(pip install python_jwt)は別のライブラリで、API の仕様も異なります。誰かのコードに import jwt と書いてあれば、それは PyJWT を使っています。混乱の原因は PyPI パッケージ名(PyJWT)とインポート名(jwt)が異なる点です。特段の理由がない限り PyJWT を使用してください。

Python で RS256 の JWT をデコードするにはどうすればよいですか?

PyJWT と cryptography バックエンドの両方をインストールします:pip install PyJWT cryptography。次に、PEM エンコードされた公開鍵を key 引数に渡し、algorithms=["RS256"] を指定します。PyJWT は RSA 署名検証を cryptography ライブラリに委譲します。cryptography パッケージがインストールされていない場合、RSA や EC アルゴリズムを使用しようとするとエラーが発生します。

Python
import jwt

public_key = open("public_key.pem").read()

payload = jwt.decode(
    token,
    public_key,
    algorithms=["RS256"],
    audience="https://api.example.com"
)

PyJWT が ExpiredSignatureError を発生させる原因は何ですか?

PyJWT はデフォルトで exp(有効期限)クレームを検証します。現在の UTC 時刻が exp タイムスタンプを過ぎている場合、jwt.ExpiredSignatureError が発生します。leeway パラメータで時計のずれ許容範囲を設定できます:jwt.decode(token, key, algorithms=["HS256"], leeway=timedelta(seconds=30))。これにより 30 秒の猶予が与えられます。有効期限の検証を完全に無効にするには(本番環境では非推奨)options={"verify_exp": False} を渡します。

Python
import jwt
from datetime import timedelta

try:
    payload = jwt.decode(token, secret, algorithms=["HS256"], leeway=timedelta(seconds=30))
except jwt.ExpiredSignatureError:
    print("Token has expired — prompt re-authentication")

ライブラリなしで Python から JWT のクレームを読み取れますか?

はい。トークンをドットで分割し、2 番目のセグメント(ペイロード)を取り出します。長さが 4 の倍数になるよう = 文字でパディングし、base64url デコードして JSON をパースします。クレームの dict が得られますが署名は検証されません。PyJWT をインストールできない制限された環境や、簡易なデバッグスクリプトに有用です。

Python
import base64, json

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSJ9.signature"
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)  # fix padding
claims = json.loads(base64.urlsafe_b64decode(payload_b64))
print(claims)  # {'sub': 'usr_8f2a'}

PyJWT で audience クレームを検証するにはどうすればよいですか?

jwt.decode() に audience パラメータを渡します:jwt.decode(token, key, algorithms=["HS256"], audience="https://api.example.com")。PyJWT はトークン内の aud クレームと指定した値を照合します。トークンに aud クレームがない場合、または値が一致しない場合は jwt.InvalidAudienceError が発生します。複数の API 向けトークンを受け付けるサービスでは、許容する audience のリストを渡すこともできます。

Python
import jwt

payload = jwt.decode(
    token,
    secret,
    algorithms=["HS256"],
    audience=["https://api.example.com", "https://admin.example.com"]
)

関連ツール

DV
Dmitri VolkovDevOps Engineer & Python Automation Specialist

Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.

MS
Maria Santos技術レビュアー

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.