JWT のデコードは中身を読むだけです。信頼できるかどうかは署名と時間で決まります
JWT(JSON Web Token)は、ドット(.)で区切られたヘッダー、ペイロード、署名の 3 つの区間からなる文字列です。前の 2 つの区間は JSON を Base64url で表記したものなので、鍵がなくても誰でもデコードして読めます。そのため、デコードして中身が見えたというだけでは、そのトークンが本物かどうか、今使ってよいかどうかは分かりません。サーバーは、署名が一致するか、有効期限(exp)が過ぎていないか、発行者(iss)と対象者(aud)が期待どおりかを順に確認してから、トークンを受け入れます。
このツールは、貼り付けたトークンをすぐにデコードし、ヘッダーとペイロードの値と意味を表で示します。あわせて、同じ順序で 4 つの関門(形式、署名、時間、発行者・対象者)をたどり、どこで止まるかを表示します。秘密鍵または公開鍵(PEM、JWK、JWKS)を入力すると署名を実際に検証し、exp までの残り時間はタイムライン上で秒単位に減っていきます。トークンと鍵はブラウザ内でのみ計算し、どこにも送信しません。
JWT を自分でデコードしたい場面
- API が 401 Unauthorized を返すとき: 拒否される理由の多くは、期限切れ(exp)、別のサービス用のトークン(aud)、鍵のローテーション(kid)のいずれかです。トークンを貼り付ければ、4 つの関門のどこで止まっているかがすぐに分かります。
- ログインが想定より早く切れるとき: iat と exp の差がトークンの有効期間です。アクセストークンは有効期間を 15 分や 1 時間のように短く設定することが多いため、画面の有効期間と残り時間から設定値を確認できます。
- OpenID Connect のログインを組み込むとき: ID トークンの iss、aud、nonce、email_verified などの値が期待どおりに入っているかを確認します。プロバイダーの jwks_uri から取得した JSON をそのまま入力すると、kid が一致する公開鍵で署名を検証します。
- 権限がない理由を調べるとき: scope、roles、groups などのクレームに必要な権限が含まれているかを、チップに分けて表示します。値の意味は発行元のサービスが決めるため、サーバー側の設定と併せて比較してください。
- サーバーの時計と端末の時計がずれているとき: 発行直後なのに「使用開始前」や「期限切れ」と表示される場合は、時計のずれが原因かもしれません。許容誤差を 0 秒、60 秒、5 分に切り替えて、判定がどう変わるかを確認できます。
- テスト用のトークンが必要なとき: 「JSON を編集」でペイロードを書き換え、秘密鍵で再署名すれば、任意の値を持つトークンを作れます。鍵なしで編集すると署名が一致しなくなることも、その場で確認できます。
使い方
- トークンを入力する: トークン欄に JWT を貼り付けます。
Authorization: Bearer …ヘッダー、トークン応答の JSON({"access_token": "…", "id_token": "…"})、コールバック URL の#id_token=…のように他の文字列と混ざっていても、トークン部分だけを探して読み取ります。トークンが複数見つかった場合は、チップで選択します。 - 区間を確認する: トークンの文字列はヘッダー、ペイロード、署名の色で分かれ、区間バーに区間ごとのバイト数が表示されます。表の行にカーソルを合わせると、その値がトークンのどの文字に対応するかが強調されます。
- 時間を確認する: 有効時間の欄に、期限までの残り時間、iat・nbf・exp を端末のタイムゾーンに変換した時刻、トークンの有効期間が表示されます。サーバーで使っているライブラリに合わせて、許容誤差を選択します。
- 署名を検証する: alg が HS256 などの HS 系なら秘密鍵を、RS・PS・ES・EdDSA 系なら公開鍵を入力します。サーバーが期待する発行者と対象者も入力すると、4 つ目の関門まで確認します。
- 編集する: 「内容」の「JSON を編集」を押すと、ヘッダーとペイロードを直接書き換えられます。署名検証欄に HS の秘密鍵または署名用の秘密鍵があれば、編集した内容で再署名したトークンを生成します。
ドット 2 つで区切られた 3 つの区間
ヘッダーとペイロードは、UTF-8 の JSON を Base64url に変換した値です。Base64url は、通常の Base64 の + と / の代わりに - と _ を使い、末尾の = を省く方式(RFC 7515 2 節)なので、URL や HTTP ヘッダーにそのまま入れられます。署名は「ヘッダー.ペイロード」の文字列全体を鍵で計算したバイト列を同じ方式で表記した値のため、ヘッダーかペイロードの 1 文字が変わっただけで一致しなくなります。次の表は、秘密鍵 demo-secret-for-roberin-jwt-2026 で作成した HS256 のトークンを区間ごとに分けたものです。
| 区間 | トークンの文字列 | デコード結果 | 文字数 | バイト |
|---|---|---|---|---|
| ヘッダー | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 | {"alg":"HS256","typ":"JWT"} | 36 | 27 |
| ペイロード | eyJzdWIiOiJ1c2VyLTEwMjQiLCJpYXQiOjE3MDQwNjcyMDAsImV4cCI6MTcwNDA2ODEwMH0 | {"sub":"user-1024","iat":1704067200,"exp":1704068100} | 71 | 53 |
| 署名 | 5YZB1gnh7IFicIbrPtT-mJoFb3uuYFcYTk84hPIA7Ro | HMAC-SHA256 の値(JSON ではない) | 43 | 32 |
3 バイトが 4 文字になるため、Base64url の区間は元の JSON より約 3 分の 1 長くなります。署名区間の長さはアルゴリズムで決まります。HS256 は 32 バイト(43 文字)、RS256 は RSA 鍵の長さと同じ(2048 ビットの鍵なら 256 バイト、342 文字)、ES256 は 64 バイト(86 文字)です。区間が 5 つある場合は、暗号化された JWE(RFC 7516)です。JWE のヘッダーは読めますが、残りの 4 つは受信側の鍵がないと復号できません。
よく見るクレームと確認のポイント
ペイロードの各項目をクレームと呼びます。RFC 7519 が登録した 7 つと、OpenID Connect が定めた名前は意味が決まっており、それ以外の名前は発行元のサービスが独自に定めた値(Private Claim)です。表の「確認のポイント」は、トークンを受け取るサーバーが行うべきことです。
| クレーム | 意味 | 確認のポイント |
|---|---|---|
iss | 発行者 | 信頼する発行者の値と 1 文字まで一致する必要があります。URL 末尾の / が 1 つ違うだけでも別の値です。 |
sub | サブジェクト(ユーザー ID) | 発行者の中でのみ一意なので、iss と組み合わせてユーザーを区別します。 |
aud | 対象者 | 文字列または配列です。自分の名前が含まれていなければ拒否する必要があります(RFC 7519 4.1.3)。 |
exp | 有効期限 | 現在時刻が exp と同じか、それより後なら期限切れです。時計のずれに備えて、数分以内の許容誤差を設けることもできます。 |
nbf | 使用開始時刻 | 現在時刻が nbf より前なら、まだ受け入れてはいけません。 |
iat | 発行時刻 | トークンがどのくらい前のものかを見るときに使います。それ自体で失効を決めるものではありません。 |
jti | トークン ID | 使用済みの jti を記録しておけば、同じトークンの再利用を防げます。 |
nonce | リクエスト確認値(OIDC) | ログインリクエスト時に送った値と一致する必要があります。異なれば、別のログインのトークンです。 |
azp | トークンを受け取ったアプリ(OIDC) | 存在する場合は、自分の client_id と一致するかを確認します。 |
scope | スコープ | スペース区切りの一覧です。要求された操作に必要なスコープが含まれているかを確認します。 |
exp・nbf・iat の読み方と時計の許容誤差
3 つの時刻はいずれも、1970 年 1 月 1 日 0 時(UTC)からの経過秒数です(RFC 7519 の NumericDate)。同じ瞬間を日本時間で表すと、UTC に 9 時間が加わります。ミリ秒で記述した値は 13 桁になり、これを秒として読むと数万年先になって、トークンは事実上失効しません。このツールは、そのような値を見つけると警告を表示します。
| 値 | UTC | 日本時間(UTC+09:00) | 説明 |
|---|---|---|---|
1704067200 | 2024-01-01 00:00:00 | 2024-01-01 09:00:00 | 前掲のサンプルトークンの iat |
1704068100 | 2024-01-01 00:15:00 | 2024-01-01 09:15:00 | サンプルトークンの exp、有効期間 900 秒 = 15 分 |
2000000000 | 2033-05-18 03:33:20 | 2033-05-18 12:33:20 | 10 桁の秒の値 |
1704067200000 | (秒として読むと 55969 年) | - | ミリ秒を誤って記述した値、1000 で割る必要がある |
受信側のサーバーの時計と発行側のサーバーの時計は、数秒から数分ずれることがあります。RFC 7519 は、exp と nbf を確認するときに「通常は数分を超えない」小さな許容誤差を設けてもよいとしています。実際の既定値はライブラリごとに異なるため、同じトークンが、あるサーバーでは受け入れられ、別のサーバーでは拒否されることがあります。
| ライブラリ | 設定名 | 既定値 |
|---|---|---|
| PyJWT(Python) | leeway | 0秒 |
| jsonwebtoken(Node.js) | clockTolerance | 0秒 |
| Spring Security(Java) | JwtTimestampValidator clockSkew | 60秒 |
| Microsoft.IdentityModel(.NET) | TokenValidationParameters.ClockSkew | 5分(300秒) |
このツールの判定は jsonwebtoken と同じです。現在時刻(秒未満は切り捨て)が exp + 許容誤差と同じか、それより後なら期限切れ、nbf が現在時刻 + 許容誤差より後なら使用開始前です。有効期限は過ぎていても許容誤差の範囲内であれば、時間の関門は通過として扱い、残りの猶予時間を表示します。
署名検証: アルゴリズムごとに必要な鍵
ヘッダーの alg は、どの方式で署名したかを示す名前です。受信側がこの値をそのまま信用してはいけないため、サーバーは許可するアルゴリズムをあらかじめ決めておき、それ以外は拒否する必要があります(RFC 8725 3.1)。このツールは、alg に合った鍵の種類を案内し、合わない鍵が入力された場合は理由を表示します。
| alg | 検証に使う鍵 | 入力できる形式 | ルール |
|---|---|---|---|
HS256·HS384·HS512 | 作成側と同じ秘密鍵 | 文字列そのまま、Base64 値、JWK(kty oct) | 鍵の長さがハッシュ長以上であること(HS256 は 256 ビット、RFC 7518 3.2) |
RS256·RS384·RS512 | RSA 公開鍵 | PEM 公開鍵、PKCS#1、X.509 証明書、JWK、JWKS | 2048 ビット以上(RFC 7518 3.3) |
PS256·PS384·PS512 | RSA 公開鍵 | RS 系と同じ | RSA-PSS、ソルト長 = ハッシュ長(RFC 7518 3.5) |
ES256 | P-256 公開鍵 | PEM、証明書、JWK(kty EC)、JWKS | 署名は R と S を連結した 64 バイト(RFC 7518 3.4) |
ES384 | P-384 公開鍵 | ES256 と同じ | 署名は 96 バイト |
ES512 | P-521 公開鍵 | ES256 と同じ | 署名は 132 バイト(曲線名は P-521) |
EdDSA·Ed25519 | Ed25519 公開鍵 | PEM、JWK(kty OKP) | RFC 9864(2025 年 10 月)は、EdDSA の代わりに Ed25519 という名前を使うよう定めている |
秘密鍵を入力した場合も公開鍵を取り出して検証しますが、検証に必要なのは公開鍵だけです。秘密鍵をコピーするときは末尾に改行が付くことが多く、入力したままでは一致せず、前後の空白を除いたときに一致する場合は、その旨をお知らせします。署名が DER 形式(0x30 で始まる)の ES のトークンは、他のライブラリが誤って作成したものなので、原因を別に表示します。
検証の流れがサーバーの確認順序と同じ理由
RFC 7519 7.2 節は、JWT を受信したときに、区間を分割し、Base64url と JSON をデコードし、署名を検証する順序を定めています。どの段階で失敗しても、トークンを拒否する必要があります。それに加えて RFC 8725 は、iss がある場合は署名鍵がその発行者のものか確認すること(3.8)、1 つの発行者が複数のサービスにトークンを出す場合は aud がない、または自分宛てでないトークンを拒否すること(3.9)を求めています。このツールの 4 つの関門はこの順序に従っており、最初に止まった関門の理由を判定の行に表示します。署名を検証していない状態では、他の関門を通過しても「未確認」のままにします。
権限が正しいか、すでにログアウトしたセッションか、同じ jti を再利用していないかは、トークンだけでは分かりません。これらの条件はサーバーが持つ記録と設定で確認するため、4 つの関門をすべて通過したというのは「トークン自体の条件は満たしている」という意味です。
JSON の編集と再署名
「JSON を編集」でヘッダーやペイロードを変更すると、ツールが新しいトークンを生成してトークン欄に入れます。署名検証欄に HS の秘密鍵、または RS・PS・ES・Ed25519 の秘密鍵(PKCS#8、PKCS#1、EC 秘密鍵、d を含む JWK)があれば、その鍵で再署名します。公開鍵だけの場合や鍵がない場合は、元の署名をそのまま付けるため、署名の関門が不一致に変わります。サンプルの「内容を改ざんしたトークン」がまさにこのケースです。role の値を user から admin に書き換えましたが、署名は元のままなので、秘密鍵で検証するとすぐに判明します。
「iat を現在に、exp を 15分後・1時間後・1日後」のボタンは、テスト用トークンの時刻をまとめて書き換えます。nbf がある場合は、nbf も現在時刻に合わせます。編集した JSON は空白だけを除いて 1 行にするため、キーの順序と数値の表記は入力したままになります。
よくある間違いと既知の攻撃
- alg none を受け入れるサーバー: 署名のないトークンは誰でも作れます。大文字小文字だけを変えた None や NONE を受け入れてしまったライブラリもあったため、許可リストで防ぐ必要があります(RFC 8725 2.1)。
- RS256 のトークンを HS256 にすり替える攻撃: 攻撃者が alg を HS256 に書き換え、サーバーの公開鍵を HMAC の秘密鍵として署名する手法です。1 つの鍵を 1 つのアルゴリズムにのみ使えば防げます。このツールは、HS のトークンに公開鍵が入力されると、この形だと知らせます。
- 推測されやすい秘密鍵: HS256 のトークンを 1 つ入手されると、辞書攻撃で秘密鍵を見つけられる恐れがあります。秘密鍵はランダムに生成し(RFC 8725 3.5)、長さはハッシュ長以上、HS256 なら 32 バイト以上にしてください(RFC 7518 3.2)。
- aud を確認しないサーバー: 同じ発行者が別のサービスに発行したトークンを、そのまま受け入れてしまいます。ID トークンを API のアクセスに使う誤りも、同じ理由で防ぐ必要があります。
- kid・jku をそのまま使う鍵の検索: kid をファイルパスや SQL にそのまま入れたり、ヘッダーの jku のアドレスから鍵を取得したりすると、攻撃者が選んだ鍵で検証することになります(RFC 8725 3.10)。
- ペイロードに入れた個人情報: 署名付き JWT は暗号化されていません。メールアドレスや電話番号を入れると、トークンが通るログやブラウザのあらゆる場所で読み取れます。
- コピー時に付いた文字: Bearer、引用符、改行が一緒に入ると、サーバーはトークンを読み取れません。このツールは、そのような文字を除いて読み取り、何を除いたかを表示します。
制限事項と注意
- JWE(5 区間)は、ヘッダーのみ読み取り、復号はしません。ネストした JWT、b64 が false のトークン(RFC 7797)、JSON シリアライズ形式の JWS も読み取りません。
- 署名検証は、ブラウザの Web Crypto で行います。ES256K と Ed448 は検証できず、Ed25519 は未対応の古いブラウザでは検証できないと表示します。
- jku・x5u のアドレスやプロバイダーの jwks_uri から鍵を取得することはありません。公開鍵は手動で貼り付ける必要があり、これはトークンを外部へ送信しないための仕様です。
- 時間の判定は、この端末の時計を基準に行います。端末の時計が狂っていると、残り時間と判定もその分だけ狂います。
- 4 つの関門を通過しても、権限、ログアウトの有無、再利用の有無はサーバーにしか分かりません。運用中の秘密鍵や実際のユーザーのトークンは、できるだけ貼り付けず、テスト用の鍵とトークンで確認してください。
基準: JWT・JWS・JWE・JWK・JWA の規則は RFC 7519・7515・7516・7517・7518、セキュリティの推奨事項は RFC 8725、Ed25519 の名称は RFC 9864、ID トークンのクレームは OpenID Connect Core 1.0 に基づいています。許容誤差の既定値は各ライブラリのソースコードを 2026 年 10 月 22 日に確認しました。表のトークンと時刻は、このツールと Python の PyJWT 2.13 で同じ値になることを確認しています。
出典
- 01IETF — RFC 7519: JSON Web Token (JWT)2 節 NumericDate、4 章 クレーム名の一意性、4.1 登録クレーム(iss・sub・aud・exp・nbf・iat・jti)、4.1.4 exp の許容誤差、7.2 検証の順序
- 02IETF — RFC 7515: JSON Web Signature (JWS)2 節 Base64url(末尾の = を省略)、4 章 ヘッダー名の一意性、4.1 登録ヘッダー(alg・kid・jku・x5u・typ・cty・crit)
- 03IETF — RFC 7518: JSON Web Algorithms (JWA)3.2 HMAC の鍵長、3.3・3.5 RSA 2048 ビットと PSS のソルト長、3.4 ECDSA 署名 R‖S
- 04IETF — RFC 8725: JSON Web Token Best Current Practices2.1 none とアルゴリズムのすり替え、2.2 弱い共通鍵、3.1 許可するアルゴリズム、3.8 署名鍵が iss の発行者のものか確認、3.9 aud の確認、3.10 kid・jku・x5u、3.11 typ
- 05IETF — RFC 7516: JSON Web Encryption (JWE)7.1 Compact シリアライズの 5 区間
- 06IETF — RFC 7517: JSON Web Key (JWK)JWK・JWK Set(keys 配列)、kid・use・alg パラメーター
- 07IETF — RFC 9864: Fully-Specified Algorithms for JOSE and COSE2025 年 10 月、Ed25519・Ed448 の登録、EdDSA を非推奨(Deprecated)に指定
- 08OpenID Foundation — OpenID Connect Core 1.02 節 ID トークン(auth_time・nonce・acr・amr・azp)、5.1 標準クレーム(name・email・email_verified など)
- 09IETF — RFC 9068: JWT Profile for OAuth 2.0 Access Tokenstyp の at+jwt、client_id・scope クレーム
- 10PyJWT — jwt/api_jwt.pydecode の leeway の既定値は 0
- 11auth0/node-jsonwebtoken — verify.jsclockTolerance がなければ 0、現在 ≥ exp + 許容誤差で期限切れ
- 12Spring Security — JwtTimestampValidatorDEFAULT_MAX_CLOCK_SKEW は 60 秒
- 13Microsoft.IdentityModel.Tokens — TokenValidationParametersDefaultClockSkew は 300 秒(5 分)
- 14MDN — SubtleCrypto.verify()ブラウザで HMAC・RSASSA-PKCS1-v1_5・RSA-PSS・ECDSA・Ed25519 の署名を検証する Web Crypto API
よくある質問
JWT をデコードすれば、そのトークンが有効かどうか分かりますか?
いいえ、分かりません。ヘッダーとペイロードは Base64url なので誰でもデコードでき、中身が見えるというのは形式が正しいという意味にすぎません。有効かどうかは、署名が一致するか、exp が過ぎていないか、iss と aud が期待どおりかまで確認して初めて分かります。このツールの 4 つの関門が、この順序に従っています。
JWT のペイロードは暗号化されていますか?
署名付きの JWT(ドット 2 つ、3 区間)は暗号化されていません。署名は内容が改ざんされていないことを保証するだけで、内容を隠すものではありません。メールアドレスや電話番号のように他人に見られてはいけない値は、ペイロードに入れないか、暗号化された JWE を使う必要があります。
exp がまだ過ぎていないのに 401 になるのはなぜですか?
署名鍵が入れ替わって kid に合う公開鍵がない場合、aud がその API の名前と異なる場合、iss がサーバー設定と 1 文字でも違う場合に拒否されます。サーバーの時計が進んでいると、期限直前のトークンが先に期限切れと判定されることもあります。ログアウトなどでサーバーがトークンを個別に失効させた場合は、トークンだけでは分かりません。
HS256 の秘密鍵を入力したのに、署名が不一致になります。
まず、サーバー設定の秘密鍵が通常の文字列か Base64 の値かを確認し、「文字列そのまま」と「Base64 値」を切り替えてみてください。コピー時に付いた改行は、ツールが除いて再度確認します。それでも一致しない場合は、別の環境(開発・本番)の鍵か、署名後にヘッダーやペイロードが書き換えられたトークンです。
RS256 のトークンを検証する公開鍵は、どこで入手できますか?
OpenID Connect のプロバイダーなら、発行者のアドレスの末尾に /.well-known/openid-configuration を付けた文書の jwks_uri に、公開鍵の一覧(JWKS)があります。その JSON をそのまま貼り付けると、トークンのヘッダーの kid と同じ鍵をツールが選んで検証します。PEM 形式の公開鍵や X.509 証明書を入力することもできます。
ここにトークンを貼り付けても安全ですか?
トークンと鍵はこのブラウザ内でのみ計算し、サーバーへの送信も保存もしません。ただし、運用中のアクセストークンは期限切れまで実際に使える鍵なので、画面の共有やキャプチャには注意し、できるだけテスト用のトークンで確認してください。
exp の数値は、どの単位ですか?
1970 年 1 月 1 日 0 時(UTC)からの経過秒数です。1704067200 は 2024 年 1 月 1 日 0 時 UTC で、日本時間では同じ日の午前 9 時です。13 桁の値はミリ秒で誤って記述したもので、秒として読むと数万年先になるため、1000 で割る必要があります。
ドットが 4 つ(区間が 5 つ)あるトークンは何ですか?
暗号化された JWE です。読めるのは最初の区間であるヘッダーだけで、残りは受信側の鍵で復号しないと内容が見えません。このツールが表示するのは、JWE ヘッダーの alg と enc、各区間のバイト数までです。