メインコンテンツへ移動

JWTの構造

JSON Web Tokenは、ドットで連結された3つのBase64urlセグメントです。シグネチャアルゴリズムを名指すヘッダー、あなたが誰で、いつまでなのかというクレームを運ぶペイロード、そして最初の2つに対する暗号学的シグネチャ。RFC 7519で定義されたこの構造は、誰でも確認できます。シグネチャが守るのは完全性であって、秘密ではありません。

端末内で処理

JWTの構造とは

サービスが、共有セッションストアなしでどのサーバーでも検証できるログイン証明をあなたに渡したいとき、渡されるのがJWTです。トークンはコンパクトでURLセーフな文字列——見たことがあるはずです。2つのドットで区切られた3つの、一見デタラメな塊が、AuthorizationヘッダーやCookieに乗ってやってきます。あのデタラメは暗号化ではありません。Base64urlであり、1行のコードでデコードできます。

このフォーマットは仕様のファミリーに由来します。RFC 7519がトークンそのものを定義し、RFC 7515(JWS)がシグネチャがそれをどう包むかを定義し、JOSEワーキンググループが登録済みクレーム名とアルゴリズム識別子を提供しました。この系譜こそ、フォーマットの最も誤解されやすい性質——後ほど平たく述べますが、署名済みとは秘密ではなく、改ざん検出可能という意味——を説明します。

このガイドでは、本物のトークンをセグメントごとにデコードし、登録済みクレームをその正確な意味とともに一覧し、遭遇する2つのシグネチャファミリー——HS256とRS256——を比較し、トークンを安全に保つルールを述べます。検証が、発行者の鍵とともにサーバーで、すべてのリクエストに対して行われるべきである理由も含めて。

以下に挙げる作業は、本物のトークンに対してなら数秒でできますJWTデコーダーは、3つのセグメントすべてをローカルで分割・デコードし、タイムスタンプを見つけては印をつけて進みます。

すべてのセグメントがBase64urlでできているため、あわせて読みたいのがBase64ガイドです。使用する文字セット、省略されるパディング、そしてトークンがURLセーフである理由を解説しています。

3つのセグメント、2つのドット

コンパクトな直列化はheader.payload.signatureです。各セグメントは独立にBase64urlエンコードされ、ドットは文字通りの区切りです。このエンコーディングは意図的にパディングを落としており、トークンは英字、数字、ハイフン、アンダースコアだけで構成されます。エスケープなしでURL、ヘッダー、Cookieの中で安全です。最初の2つのセグメントのデコードは、ハックではありません。トークンを調べるための、意図された方法です。検証が行われるのは3つ目のセグメントです。

シグネチャがその上で計算された本物のトークンは、正確に次のようにデコードされます:

有効なHS256シグネチャを持つトークン

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyXzg0OTIiLCJhdWQiOiJhcGkuZXhhbXBsZS5jb20iLCJpYXQiOjE3NjcyMjU2MDAsImV4cCI6MTc2NzMxMjAwMCwianRpIjoiYjBmNmMyZDQtNGU3YS00YzFmLTlhMmItM2Q1ZTdmOWExYzNlIn0.iDPqw4BRxZtM064Y3EH83XLB0cgEjbj5snSXeM846VY

デコードされた最初の2つのセグメント

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

{"iss":"https://auth.example.com","sub":"user_8492",
 "aud":"api.example.com","iat":1767225600,
 "exp":1767312000,
 "jti":"b0f6c2d4-4e7a-4c1f-9a2b-3d5e7f9a1c3e"}

ヘッダー:トークンの目次

ヘッダーは小さなJSONオブジェクトです。正式にはJWS Protected Header。ほぼすべての仕事をするのは2つのフィールドです。algは署名アルゴリズムを名指し、typはメディアタイプを記録します。慣習上はJWTです。3つ目のkid(キーID)は、発行者が複数の鍵を公開しているとき、検証者を正しい鍵へ導きます。ヘッダーは署名されますが、暗号化は決してされません。そして信頼できるのは、シグネチャの検証が通ったあとだけです。その時点までは、algは事実ではなくクレームです。

フィールド意味例
algシグネチャアルゴリズム——検証者の最初の振り分け判断HS256, RS256, ES256
typメディアタイプ。慣習上はJWT、多くのライブラリは無視するJWT
kid複数が公開されているとき、どの鍵で検証するか2026-01-signing
none「署名なし」を意味する疑似アルゴリズム——本当に要求されていないかぎり拒否(既知の攻撃標的)

ペイロード:登録済みクレームと、それぞれが約束するもの

ペイロードはクレームのJSONオブジェクトです。7つはRFC 7519に登録され、正確で相互運用可能な意味を持ちます。それ以外はすべて、発行者と受け手の間の私的なものです。時刻はNumericDate値——Unixエポックからの秒数であって、ミリ秒ではありません——であり、時刻系のクレームは検証者への助言にすぎません。確認するのは検証者の仕事であって、フォーマットが強制するものではありません。

クレーム正式名称主張する内容
iss発行者トークンを作成し署名したのは誰か——URLまたは名前
subサブジェクトトークンの対象は誰か——ユーザーまたはクライアントの識別子
audオーディエンストークンがどの受け手向けか。自分がリストに載っていなければ、検証者は拒否する
exp有効期限エポックからの秒数による期限。過ぎれば無効
nbf有効開始時刻トークンが受け入れられうる最も早い時刻
iat発行時刻トークンが作成された時刻——古さの証拠であって、期限ではない
jtiJWT IDこのトークンの一意な識別子——失効リストを引くためのフック

シグネチャ:HS256、RS256、そして検証の仕組み

シグネチャは、署名入力——エンコード済みのヘッダー、1つのドット、エンコード済みのペイロード——に対して、ヘッダーが名指すアルゴリズムで計算されます。HS256は共有秘密鍵を使うHMAC-SHA256です。高速で対称ですが、署名者とすべての検証者が単一の信頼ドメインを成すときにだけ適切です。秘密鍵を持つ者は誰でもトークンを作れてしまうからです。RS256はRSAです。発行者が秘密鍵で署名し、誰でも——ブラウザを含めて——公開されている公開鍵で検証できます。これこそ、OAuth 2.0やOpenID Connectのようなフェデレーテッドプロトコルの既定になっている理由です。

検証は、シグネチャを再計算して比較します。メカニズムはそれだけです。だからこそ、検査がどう走るかを決めるのは、algヘッダーひとつです。ライブラリは2010年代前半のアルゴリズム混同攻撃のあと、自らを固めました。algにnoneを主張するトークンや、公開鍵をHMACの秘密鍵に見立ててHS256として再生されたRS256トークンが、検証を通るのは、期待するアルゴリズムを固定し損ねた検証者に対してだけです。

2つのファミリーをひと目で

HS256: HMAC-SHA256(secret, header + '.' + payload)
       署名者と検証者が秘密鍵を共有する

RS256: RSA-SHA256(private key, header + '.' + payload)
       発行者が署名し、誰でも公開鍵で検証できる

そこから導かれるルール

発行者ごとに想定するアルゴリズムを固定する。
トークン自身のヘッダーだけから決めない。
alg: none は決して受け入れない。

署名はされても封はされていない:シグネチャがしないこと

シグネチャが証明するのは、トークンが鍵の持ち主によって発行され、それ以降改ざんされていないこと——完全性と真正性です。機密性はゼロです。Base64urlはエンコーディングであり、ペイロードはトークンを見た者すべてが読めます。ブラウザ拡張、URLでトークンが運ばれるときの中継プロキシ、ログ収集者も含めて。そこからいつも守るべきルールが導かれます。JWTペイロードに秘密を決して入れない。トークンをURLで決して送らない。そして、デコードしたすべてのクレームを、判断ではなくデータとして扱う。

最後の点こそ、クライアント側の罠です。ブラウザはトークンをデコードしてexpやsubを読めます。しかしトークンを実質的に検証することはできません。発行者の鍵を持っていないからです。HS256では、秘密鍵はそもそもブラウザに届いてはならず、RS256では、クライアント側の検査は保持者が何にアクセスできるかについて何も証明しません。クライアントは、表示のためにトークンを調べることはできます。名前を見せる、期限切れを警告する、といったことです。しかし、シグネチャとすべてのクレームを検証するのは、毎回のリクエストでサーバーの仕事です。調べることは検証ではなく、読めることは認可されたことではありません。

トークンを安全に調べる——実務上のループ

規律ある検査ループは数秒で済みます。ヘッダーとペイロードをデコードし、時計と突き合わせてexpとnbfを確認し、issとaudが期待どおりか確かめる。そしてそのときはじめて——サーバーで——固定したアルゴリズムと正しい鍵でシグネチャを検証します。最終検証以外はすべて純粋なテキスト処理です。だからこそ、完全にブラウザ内で動作するデコーダーこそ適切なツールなのです。本物のトークンはベアラークレデンシャルであり、クレデンシャルは自分が管理するタブの中に置くべきものであって、転送するかもしれないウェブサイトの中に置くべきものではありません。

この構造が与えてくれないものも覚えておいてください。失効——jtiベースのブロックリストはサーバー側で構築し、確認しなければなりません。機密性——暗号化されたトークンが必要ならJWEを使います。そして信頼——設定したことのない発行者からの、構造的に完璧なトークンは無価値です。構造は何が約束されたかを教えます。約束が守られたままであることを教えるのは、検証だけです。

よくある質問

本物のトークンをJWTデコーダーに貼り付けても安全ですか?

このサイトなら安全です。デコーダーが完全にブラウザ内で動作するためです。トークンは決してアップロードされず、サーバーはそれを記録できません。さらにサイトのContent-Security-Policyが外向きのリクエストを遮断するため、ページは事故でも送信できません。見知らぬオンラインデコーダーにははるかに注意してください。JWTはベアラークレデンシャルであり、読んだ者は誰でも、期限が切れるまでそれを使えます。

シグネチャはトークンの内容を暗号化しますか?

いいえ——シグネチャは改ざんを検出するだけです。Base64urlは可逆のエンコーディングであって暗号化ではありません。トークンを持つ者は誰でも、すべてのクレームを読めます。だからこそ、パスワードや秘密はペイロードに入れてはならないのです。機密性のある変種はJWE——暗号化されたJSON Web Token——として存在しますが、普通のAuthorizationヘッダーに乗っているトークンはJWSです。署名済みで、読めて、改ざん検出可能です。

HS256とRS256の違いは何ですか?

鍵のモデルです。HS256は1つの共有秘密鍵によるHMAC-SHA256で、トークンを検証できる当事者は誰もがトークンを作ることもできるため、両端を管理する単一のサービスに向きます。RS256はRSAです。発行者が秘密鍵で署名し、検証者は公開鍵で確認します。検証は、偽造の力を決して与えません。フェデレーテッドID標準がRS256を基盤にするのは、まさにその理由からです。

JWTをサーバーで検証しなければならないのはなぜですか?

検証には発行者の鍵と認可の判断が必要で、その両方を持つのはサーバーだけだからです。HS256では、共有秘密鍵はそもそもブラウザに届いてはなりません。RS256では、ブラウザも暗号学的にシグネチャを確認できますが、その結論はアクセスについて何も証明しません。クライアントは改変されうるからです。サーバーは、すべてのリクエストでシグネチャを再検証し、iss、aud、expを確認します。

JWTが期限切れになると、何が起こりますか?

自動的には何も起こりません。expは、検証者が確認すべきクレームです。仕様に適合するサーバーは現在時刻と比較し、過ぎたらトークンを拒否します。ずさんなサーバーは永遠に受け入れ続けます。リフレッシュフローが存在するのはこのためです。すべてのリクエストで確認される短命のアクセストークンに加えて、必要に応じてサーバーが失効させられる、有効期間の長いリフレッシュトークンを組み合わせます。

シグネチャを検証していないトークンを信用できますか?

未検証の目安としてだけです。検証されていないトークンは、誰が書いたものでもありえます。issもsubも、expさえも、シグネチャ検査が通るまではただのテキストです。安全な考え方はこうです。デコードは、トークンが何を主張しているかを教えます。検証は、その主張に誰が署名したか、そしてそれが変わっていないことを教えます。アクセスの制御を前者に基づいて行ってはいけません。

ここで引用した規格はどこで読めますか?