JWT トークンをデコードして検査する方法

· 9 分で読めます

JSON Web Tokens(JWT)は、モダンなWebアプリケーションで認証を扱う最も一般的な方法です。認証で何かがうまくいかないとき(ユーザーが予期せずログアウトされた、権限が間違っている、APIが401を返す)、JWTのデコードは通常、最初のデバッグステップです。JWTの3つの部分、標準的なクレーム、署名に使えるアルゴリズム、よくある落とし穴を理解すると、認証のデバッグは魔法のようなものから日常的な確認作業になります。

JWTの簡単な歴史

JWTは2015年5月にRFC 7519として標準化されました。それまでIETFで数年にわたるドラフトの反復がありました。フォーマットは初期のコンパクトトークン設計(SAMLアサーション、シンプルな不透明クッキー)から借りていますが、それらに欠けていた2つを加えました。あらゆる言語で読める厳格なJSON形状と、URLパラメーター、HTTPヘッダー、フォームフィールドを再エスケープなしで通過できるbase64url安全エンコーディングです。仲間の仕様、署名のためのJWS(RFC 7515)、暗号化のためのJWE(RFC 7516)、アルゴリズム名のためのJWA(RFC 7518)が、合わせてJOSE(JavaScript Object Signing and Encryption)ファミリーを形成します。

OAuth 2.0とOpenID Connectは間もなくJWTをデフォルトのトークンフォーマットとして採用しました。これが、ほぼすべてのモダンな認証プロバイダー(Auth0、Okta、Cognito、Keycloak、Firebase、Supabase、Clerk)が今日JWTを発行している理由です。自己完結型トークンとステートレスバックエンドの組み合わせは、マイクロサービスとAPIゲートウェイに非常に自然な形でフィットすることが分かりました。難点はJWTが悪用しやすいことで悪名高く、過去10年間、アルゴリズムを慎重に検証しなかったライブラリで安定したCVEの流れが生まれてきました。

JWTの中身

JWTはドットで区切られた3つの部分を持ちます:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U

ヘッダー: アルゴリズム(HS256、RS256など)とトークンタイプを含みます。

{"alg": "HS256", "typ": "JWT"}

ペイロード: ユーザーとトークンに関するクレーム(データの主張)を含みます。

{"sub": "1234567890", "name": "Alice", "exp": 1700000000}

署名: トークンが改ざんされていないことを検証する暗号学的ハッシュです。署名鍵なしではこれを読めません。

各セクションはbase64urlエンコードされています。これは+/の代わりに-_を使い、末尾の=パディングを省略します。Base64urlは暗号化ではありません。真ん中のセグメントを任意のデコーダーに貼り付けるだけでペイロードが見えます。それは設計上の意図です。中間セグメントは経路上のサービスが読めるように設計されており、署名だけが信頼性を証明する部分です。

よくあるJWTクレーム

標準クレームはIANAに登録され、RFC 7519で定義されています。ほとんどは任意ですが、以下のものはほぼ常に存在します。

クレーム正式名含まれる内容
subSubjectユーザーIDまたは識別子
expExpirationトークンの有効期限のUnixタイムスタンプ
iatIssued Atトークンが作成されたUnixタイムスタンプ
issIssuerトークンを作成した者(認証サーバー)
audAudienceトークンが対象とする者
nbfNot Beforeこの時刻より前はトークンは有効ではない
jtiJWT IDトークンの一意な識別子
azpAuthorized Partyトークンが発行された相手(OIDC)
scope / scpOAuthスコープ付与された権限、スペース区切りが多い
emailEmail標準的なOIDCユーザー識別子
nameName表示名(OIDC)
nonceNonceOIDCのリプレイ保護値
kid(ヘッダー)Key IDどの署名鍵が使われたか(JWKSルックアップ用)

標準セットを超えて、アプリケーションは独自のカスタムクレーム(rolestenant_idfeature_flagspermissions)を追加します。カスタムクレーム名はデフォルトでは名前空間化されていないため、2つの異なるサービスが同じ名前を異なる意味で使うことがあります。URIで接頭する(https://myapp.com/roles)というOIDCの慣習が衝突を回避します。

JWTをデコードする手順

  1. トークンを貼り付ける: 完全なJWT(header.payload.signature形式)をデコーダーに入力します。ブラウザベースのデコーダーはローカルで処理するため、トークンはページを離れません。
  2. デコードされたセクションを表示: ツールはヘッダー(アルゴリズム)、ペイロード(クレーム)、署名を整形されたJSONとして表示し、タイムスタンプはUnix整数と人間が読める日付の両方として示します。
  3. クレームを確認する: 有効期限、発行者、サブジェクト、オーディエンス、認可ロジックを駆動するカスタムクレームを調べます。
  4. 期待値と比較する: 発行者を設定した認証プロバイダーと、オーディエンスをトークンが送られるAPIと、ロール/スコープクレームをユーザーが持つべき権限と相互参照します。
  5. 時刻のテスト: iatnbfexpにマウスを乗せて、トークンが現在有効か、まもなく期限切れか、クロックスキューの許容範囲を超えるほど前に発行されたかを確認します。

署名アルゴリズム

すべてのJWTが同じ暗号を使うわけではありません。algヘッダーが署名がどのファミリーに属するかを示し、それぞれが大きく異なるセキュリティ特性を持ちます。

アルゴリズムファミリー鍵の種類選ぶべきとき
HS256HMAC共有秘密単一サービスのアプリ。チームをまたいで秘密を共有しない
HS384 / HS512HMAC共有秘密HS256と同じだがダイジェストが長い
RS256RSA公開鍵/秘密鍵ペアOIDCで最も一般的。検証側は公開鍵だけが必要
RS384 / RS512RSA鍵ペアRS256と同じだが鍵が大きい
PS256 / PS384 / PS512RSA-PSS鍵ペアモダンなRSA、新規導入ではRSより推奨
ES256 / ES384 / ES512ECDSA楕円曲線鍵ペアRSAより鍵が小さく、検証が高速
EdDSAEd25519Edwards曲線鍵ペア最新、最小、最速。まだ普遍的ではない
noneなしなし本番では禁止。一部の古いライブラリはまだ受け付ける

非対称アルゴリズム(RS*PS*ES*EdDSA)は、どのサービスも公開鍵だけでトークンを検証できるようにします。これがOIDCで主流である理由です。対称(HS*)は単一アプリケーション内では問題ありませんが、複数のコンシューマーにまたがるローテーションや配布は悪夢になります。

JWTでのデバッグ

トークン期限切れ? expクレームを確認します。Unixタイムスタンプを人間が読める日付に変換します。過去なら、トークンは期限切れでリフレッシュが必要です。ほとんどのJWTライブラリは期限切れトークンをデフォルトで拒否します。アプリがそれを受け付けるなら、それはセキュリティのバグです。

権限が違う? ペイロード内のロールやスコープのクレームを探します。これらは実装によって異なりますが、しばしば"role": "admin""scope": "read write profile"のように見えます。

ユーザー識別の問題? subクレームがユーザーを識別します。期待するユーザーIDと一致するか検証します。一部のプロバイダーは不透明なGUIDを使い、他はメールアドレスを使うことに注意してください。デコーダーが実際に何があるかを見せます。

トークンが受け付けられない? aud(オーディエンス)クレームを確認します。APIが特定のオーディエンス値を期待していて、トークンが別のものを持っていれば拒否されます。オーディエンスの不一致はトークンを誤ったサービスにルーティングした症状としてよくあります。

デプロイ後の401エラー? iss(発行者)クレームを確認します。新しい認証プロバイダーのテナントや切り替わった署名鍵は発行者URLを変えます。検証側がまだ古いものを信頼していると、すべてのトークンが無効に見えます。

クロックスキューの問題? iatがわずかに未来、またはexpがわずかに過去なら、サーバーの時計がずれている可能性があります。ほとんどのJWTライブラリは数秒の余裕を許可します。そうでない場合、NTPで同期した時計が問題を解決します。

よくある落とし穴

JWTの代替手段

JWTは主流ですが唯一の選択肢ではありません。各代替案は異なる特性とのトレードオフです。

メカニズム強み弱み
JWT(JWS)自己完結型、サービス間で扱いやすい追加のステートなしには取り消せない
不透明トークン + イントロスペクション取り消しが容易、クレームを隠せる毎リクエストが認証サーバーに当たる
サーバーサイドセッション最もシンプルなモデル、即座の取り消しサービス間でスケールしにくい
PASETOより安全なJWTの置き換え(algの混乱がない)エコシステムが小さい
Macaroons組み込みの減衰(委譲権)限定的なライブラリサポート
OAuth 2.0 + JWTアクセストークンAPIの業界標準仕様が大きく、誤実装しやすい
OIDC IDトークン標準的なユーザー識別 + JWTアクセストークンとよく混同される
mTLSクライアント証明書トランスポート層で最強の認証証明書管理のオーバーヘッド

ほとんどのチームにとって選択肢はJWTか不透明トークンです。検証が安価でオフラインである必要があるときはJWTが勝ち、取り消しが即座である必要があるときは不透明トークンが勝ちます。

プライバシーとデコーダー

JWTデコーダーは完全にブラウザ内で動作します。貼り付けたトークンは分割され、base64urlでデコードされ、JSONはネットワークリクエストなしでパースされ、整形されます。デコードされたトークンのログも、含まれるクレームの分析も、誰のためにデバッグしていたかを再構築する方法もありません。JWTはしばしばユーザー識別子、メールアドレス、内部のロール名、テナントIDを含みます。これらはまさに第三者のサーバーに送りたくない種類のメタデータです。クライアントサイドでデコードすることは、その情報をマシン上に保ち、認証に触れるどんなデバッグタスクにとっても正しいデフォルトです。

よくある質問

デコーダーで JWT の署名を検証できますか?

いいえ。署名検証にはサーバーに保管されている署名シークレットまたは公開鍵が必要です。デコーダーはトークンの中身を表示しますが、暗号学的検証はバックエンドで行わなければなりません。本番環境では検証されていない JWT を信頼してはいけません。

JWT をオンラインツールに貼り付けても安全ですか?

ツールがブラウザ内で動作する場合は安全です。ブラウザベースのデコーダーはトークンをローカルで処理し、サーバーには何も送信しません。トークンをネットワーク経由で送信するツールは避けてください。

exp クレームとは何ですか?

exp(expiration)クレームはトークンが期限切れになる時刻を示す Unix タイムスタンプです。この時刻を過ぎたトークンは拒否すべきです。認証問題のデバッグでは必ずこのクレームを確認してください。

JWT を暗号化することはできますか?

標準的な JWT(JWS)は署名されますが暗号化されません, 誰でもペイロードをデコードできます。JWE(JSON Web Encryption)トークンは暗号化されていますが、あまり一般的ではありません。標準の JWT ペイロードに機密データ(パスワードやシークレット)を入れることは絶対に避けてください。

What is the alg none vulnerability?

Early JWT libraries accepted tokens with an alg header set to "none", meaning the signature could be omitted entirely. An attacker who set this header could forge any payload. Modern libraries reject "none" by default, but legacy systems may still be exposed; always allow-list the expected algorithm rather than trusting the header.

How should I store a JWT on the client?

HttpOnly secure cookies with SameSite=Lax (or Strict) are the safest default; they cannot be read by JavaScript, which mitigates XSS token theft. localStorage is convenient but vulnerable to any XSS bug. Never store long-lived JWTs alongside untrusted scripts.