JWT をデコードする方法
- トークン にトークンを貼り付けます。
Authorization: Bearer eyJ…の行をまるごと貼っても、前後に引用符があっても、複数行に折り返されたトークンでも構いません。 - 結果を読みます。3 つの部分は色分けされ、ヘッダーとペイロードは整形表示され、クレーム には各クレームとその意味が並びます。
expやiatなどの時刻は、ローカル時刻、UTC、「1 時間後に期限切れ」のような相対時間で表示されます。 - 署名を確認するには、署名を検証 の下に共有シークレットか公開鍵を貼り付けます。欄のラベルはトークンの
algに応じて変わり、HS256/384/512 なら シークレット、それ以外なら 公開鍵 になります。 - 警告を読みます。
alg: none、期限切れのトークン、未来のnbf、expがないこと、ミリ秒で書かれた時刻、リモートの鍵を指すヘッダー(jku、x5u)や自前の鍵を埋め込んだヘッダー(jwk)を指摘します。
/jwt-decoder/#token=eyJ… のようなリンクでトークンを直接開くこともできます。# 以降は URL フラグメントで、ブラウザはフラグメントをサーバーに送りません。
トークンの中身
署名付きの JWT(コンパクト形式の JWS)は、3 つの Base64URL 文字列をドットでつないだもの、header.payload.signature です。以下は、2026 年 1 月 1 日に発行され 1 時間有効な、実際の HS256 トークンです。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyLTQyIiwiYXVkIjoiYXBpLmV4YW1wbGUuY29tIiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjkyMDB9.U3Gb12hN5Fnpi1rJ7nrkUzIEidkg-hPnbgiIpTah3R4
最初の部分をデコードするとヘッダー {"alg":"HS256","typ":"JWT"} になります。2 つ目はペイロードです。
{"iss":"https://auth.example.com","sub":"user-42","aud":"api.example.com","iat":1767225600,"exp":1767229200}
3 つ目の部分は、最初の 2 つの部分に対して計算した 32 バイトの HMAC-SHA256 なので、ヘッダーやペイロードを 1 文字でも変えると壊れます。ほぼすべての JWT は eyJ で始まります。これは {" の Base64 です。各部分は URLセーフなアルファベット(- と _)を使い、= のパディングはありません。手作業でのデコード手順は JWT での Base64URL の使われ方 で解説しています。
JWS と JWE:署名付きトークンと暗号化トークン
ほとんどの JWT は JWS です。署名はされていますが、誰でも読めます。JWE は暗号化されていて、3 つではなく 5 つの部分、header.encrypted_key.iv.ciphertext.tag からなります。ヘッダーは読めるままで、鍵管理アルゴリズム(alg、たとえば RSA-OAEP-256 や dir)とコンテンツの暗号方式(enc、たとえば A256GCM)を示します。
ここに JWE を貼り付けると、ツールはそのヘッダーをデコードして alg、enc、kid を表示し、ペイロードを暗号化済みとして示します。復号には受信者の秘密鍵か共有鍵が必要で、このツールは JWE を復号しません。ユーザーがペイロードを読めないトークンが必要なら、JWE が正しい形式です。JWS のペイロードに秘密の情報を入れるのは、平文で入れるのと同じことです。Base64 は暗号化ではない で説明しています。
JWT クレーム早見表
最初の 7 行は RFC 7519 の登録済みクレームで、残りは OpenID Connect や OAuth のトークンでよく使われるものです。時刻の値はすべて NumericDate、つまり 1970-01-01 UTC からの秒数(整数)で、ミリ秒になることはありません。
| クレーム | 名前 | 確認すべきこと |
|---|---|---|
iss | 発行者(Issuer) | トークンを作成・署名した主体。信頼する発行者と一致している必要があります。 |
sub | 主体(Subject) | トークンが対象とするユーザーやエンティティ。多くはユーザー ID です。 |
aud | 対象者(Audience) | トークンの受け取り手。文字列か配列で、API はほかの宛先向けのトークンを拒否すべきです。 |
exp | 有効期限 | この時刻を過ぎたトークンは拒否しなければなりません。 |
nbf | Not Before | この時刻より前は、トークンはまだ有効ではありません。 |
iat | 発行日時 | 発行された時刻。経過時間の上限を設けるのに便利です。 |
jti | JWT ID | 一意の ID。リプレイの検出や、個別のトークンの失効に使います。 |
azp | 認可された当事者 | トークンの発行先の OAuth クライアント(OpenID Connect)。 |
scope、scp | スコープ | 付与された OAuth スコープ。scp は Microsoft と Okta の表記です。 |
nonce | nonce | OpenID Connect の ID トークンを、それを要求したログインリクエストに結び付けます。 |
auth_time | 認証時刻 | ユーザーが実際にログインした時刻。iat よりかなり前のこともあります。 |
roles、groups | ロール、グループ | ID プロバイダーが追加する認可データ。 |
tid、oid | テナント、オブジェクト ID | Microsoft Entra ID のテナントとユーザーのオブジェクト識別子。 |
ヘッダーには独自のパラメーターがあります。alg(署名アルゴリズム)、typ(通常は JWT、OAuth のアクセストークンなら at+jwt)、kid(どの鍵で署名したか)、cty(トークンが別のトークンの中に入れ子になっているときに JWT を設定)です。
署名の検証:どの鍵を貼り付けるか
トークンの出どころを示せるのは署名の確認だけで、必要な鍵はヘッダーにあるアルゴリズムの系統で決まります。
alg | 貼り付ける鍵 | 署名の長さ |
|---|---|---|
| HS256、HS384、HS512 | 共有シークレット。保存形式に合わせて シークレットの形式 をテキスト、Base64/Base64URL、16進数のいずれかにします。oct の JWK も使えます。 | 32、48、64 バイト |
| RS256–RS512、PS256–PS512 | RSA の公開鍵:PEM の BEGIN PUBLIC KEY、PEM の BEGIN RSA PUBLIC KEY、X.509 証明書、JWK、JWKS。 | 鍵長。RSA-2048 なら 256 バイト |
| ES256、ES384、ES512 | P-256、P-384、P-521 の EC 公開鍵。上記のどの形式でも可。 | 64、96、132 バイト(生の r‖s) |
| EdDSA | PEM か OKP の JWK 形式の Ed25519 公開鍵。 | 64 バイト |
ID プロバイダーが発行したトークンの公開鍵は、https://<issuer>/.well-known/openid-configuration に記載された jwks_uri にあります。このページは他のサーバーに接続できないため、その URL を取得できません。新しいタブで開いて JSON を貼り付けてください。JWKS の場合は kid で鍵が選ばれ、トークンに kid がなければ、互換性のあるすべての鍵が試されます。
DER 形式の ECDSA 署名(ES256 なら 64 バイトではなく通常 70~72 バイト)は検出して変換し、その旨を示します。JWS では生の r‖s が必要で、多くのライブラリが DER を拒否するからです。秘密鍵を貼り付けても動きます(公開鍵の部分を導出します)が、検証に秘密鍵が必要になることはありません。ES256K と Ed448 は WebCrypto にないため対応していません。
正しいはずのシークレットで HS256 のトークンが失敗するなら、シークレットが設定のどこかで Base64 エンコードされていて、サーバーが使う前にデコードしていることがよくあります。シークレットの形式 を Base64 に切り替えて、もう一度試してください。
ここでトークンをデコードしても安全な理由
Authorization ヘッダーに入った JWT は、期限が切れるまで有効な認証情報です。本番環境の有効なトークンを、それをサーバーに送る Web ページに貼り付ければ、そのサーバーの運営者は API に対してトークンを再利用できます。しかも外から見て、ページがそうしているかどうかはたいてい判別できません。
このページでは、デコードも WebCrypto による署名の確認も、ブラウザ内で行われます。さらにページには Content Security Policy が付いていて、その connect-src が許可するのは、Cloudflare Web Analytics がページビューを数える /cdn-cgi/rum だけです。そのため、ページが試みるかもしれないそれ以外の fetch、XHR、WebSocket、ビーコンのリクエストは、すべてブラウザがブロックします。自分で確かめることもできます。開発者ツールのネットワークタブを開いてトークンを貼り付けてみるか、ページを読み込んだ後に Wi-Fi を切ってデコードを続けてみてください。
それでも、テスト用のトークンや期限切れ間近のトークンを使うことをおすすめします。本番のトークンを管理外の場所に貼り付けてしまったなら、そのセッションを失効させてください。
期限切れのトークン、nbf、時計のずれ
exp と nbf は検証側の時計と比べられますが、時計はずれるものです。時計が 30 秒進んでいるサーバーが発行したトークンは、iat と nbf が未来の時刻になった状態で届き、「まだ有効ではない」として拒否されることがあります。そのため JWT のライブラリは、PyJWT の leeway、jsonwebtoken と jose の clockTolerance のように、わずかな許容幅を受け付けます。
このデコーダーは時刻を端末の時計と比べ、結果を言葉で示します。exp なら「2 日前に期限切れ」、nbf なら残り時間付きで「まだ有効ではありません」と表示し、iat が未来の時刻なら発行者の時計を疑う警告を出します。1767229200000 のような値はミリ秒として警告します。秒として読むと数万年先の日時になります。Math.floor(Date.now() / 1000) ではなく Date.now() を使ったコードによくあるバグです。
検証せずにコードでクレームを読むだけなら、Python の標準ライブラリで足ります。
import base64, json
def b64url_decode(part):
return base64.urlsafe_b64decode(part + "=" * (-len(part) % 4))
header_b64, payload_b64, signature_b64 = token.split(".")
print(json.loads(b64url_decode(payload_b64)))
Node.js では JSON.parse(Buffer.from(payload_b64, "base64url").toString("utf8")) で同じことができます。認可の判断には、署名を検証し、受け付けるアルゴリズムを固定するライブラリを使ってください。ほかのスニペットは Python と Node.js のページにあります。
テスト用にトークンを作ったり署名し直したりするには、JWT エンコーダー を使います。1 つの部分だけなら Base64URL デコーダー で単独でデコードできます。
よくある質問
シークレットキーなしで JWT をデコードできますか?
できます。ヘッダーとペイロードは Base64URL でエンコードされた JSON にすぎないので、トークンを持っている人なら誰でも読めます。シークレットや公開鍵が必要なのは、署名が本物かどうかを確認するときだけです。
デコーダーが署名を無効と表示するのはなぜですか?
たいていは鍵が間違っているか、形式が違います。HS256 なら、シークレットが Base64 や16進数で保存されていないか確認し、シークレットの形式 をそれに合わせてください。RS256 や ES256 なら、公開鍵がトークンの kid に対応するものか確認してください。JWKS 全体を貼り付ければ、ツールが kid で鍵を選びます。
JWT の有効期限を確認するには?
exp クレームを見てください。1970-01-01 UTC からの秒数で、クレームの表ではローカル時刻、UTC、「2 日前に期限切れ」のような相対時間で表示されます。exp のないトークンは、それ自体では期限切れになりません。
このツールで JWE を復号できますか?
できません。5 つの部分からなる暗号化トークンについては、保護ヘッダーをデコードして alg、enc、kid を表示します。ペイロードを復号するには、受信者の秘密鍵か共有鍵が必要になります。
貼り付けたトークンはどこかに送信されますか?
送信されません。デコードと検証は JavaScript と WebCrypto によってブラウザ内で行われ、ページの Content Security Policy が、Cloudflare のページビュー計測を除くすべてのネットワークリクエストをブロックしています。