JWTデコード

現在の入力をデモデータに置き換えます。

期待される結果: ヘッダーは HS256、ペイロードは sub = demo-user です。署名は意図的に無効にしてあり、ログイン用トークンではありません。

制限事項: デコードだけでは認証できません。署名確認は HS256/384/512 に対応します。発行者、対象者、有効期間はアプリケーション側でも検証が必要です。

デコードは検証ではありません。JWTのペイロードは誰でも読めますし、正しくデコードできる偽のトークンも作成できます。デコード結果はトークンの主張を示すだけで、その内容が正しいことは保証しません。処理はすべてブラウザ内で行われ、トークンが送信されることはありません。

入力した内容が保存・記録・共有されることはありません。

JWTを貼り付けると、ヘッダーとペイロード、有効期限を確認できます。デコードはすべてブラウザ内で行われるため、トークンが端末外に出ることはありません。

使い方

  1. 入力欄にトークンを貼り付けます。
  2. ヘッダー・ペイロード・時刻クレームが下に表示されます。
  3. 必要に応じて秘密鍵を入力し、HS256またはHS512の署名を検証します。

3つのセグメントの読み方

JWTは、base64urlでエンコードされた3つのセグメントをドットでつないだものです。ここでのbase64urlはパディングなしで書かれるため、通常のBase64のようにセグメントの末尾が=で終わることはありません。

1つ目のセグメントはヘッダーです。alg に署名アルゴリズム、たいていは typ に種類を、ときには kid にキーIDを入れた小さなJSONオブジェクトで、kid によって検証側は複数の鍵のどれで検証すべきかを知ります。2つ目はペイロードで、クレームを並べたJSONオブジェクトです。3つ目は署名で、JSONではなく生のバイト列なので、ここでは16進数で表示します。

署名の対象は、最初の2つのセグメントを表示されているとおりドットでつないだもの、つまりエンコード済みのテキストであって、それが表すJSONではありません。そのため、トークンを整形したり、再シリアライズしたり、クレームの順序を入れ替えたりすると無効になります。このデコーダーがクレームを表示しつつ、元のテキストこそが重要なものとして扱うのはそのためです。

デコードは検証ではない

JWTで最も誤解されやすい点なので、はっきり言っておきます。ペイロードはエンコードされているだけで、暗号化されてはいません。トークンを持っている人なら誰でも、すべてのクレームを読めます。そして誰であっても、ペイロードに好きな内容を書いたトークンを作ることができ、それはここでもどこでも問題なくデコードできます。

デコードしたトークンから得られるのは主張の集まりであって、その証拠は何もありません。証拠は署名であり、それを確かめるには鍵が必要です。事前に決めた鍵と事前に決めたアルゴリズムでその確認を行うまで、トークンが伝えているのは、誰かがあなたに信じさせたい内容にすぎません。

実務に置き換えるとこうなります。デコードしたペイロードをもとに認可の判断を下してはいけません。デバッグ中にトークンの中身を見るためにデコードするのは問題ありません。しかし、ゲートウェイで検証済みのはずだからといって、アプリケーションのコードで検証せずに sub クレームを読むと、サービスは偽造されたIDを信用することになります。このページで任意のHS256検証を用意しているのは、まさにその違いを目に見える形にするためです。

クレームと、それを確認する責任は誰にあるか

RFC 7519は7つのクレームを登録しています。iss は発行者、sub はトークンの対象となる主体、aud は想定される受信者、jti はトークン自体の一意な識別子です。3つの時刻クレーム exp、nbf、iat はNumericDate値で、ミリ秒ではなくUNIXエポックからの秒数です。手作りのトークンが拒否される最も多い理由がこれです。

これらはどれも、ひとりでに効力を持つわけではありません。exp が過去のトークンが期限切れになるのは、検証側が時計と比べて拒否するからにすぎません。別の受信者向けに発行されたトークンが危険になるのは、誰も aud を確認しない場合です。ある サービス向けに発行されたトークンが、同じ署名鍵を共有する別のサービスに対して再利用されるのはこのためです。多くのライブラリは既定で exp を確認しますが、aud は期待値を指定しない限り確認しません。

発行側と検証側のマシンの間で時計が少しずれるのは普通のことなので、検証側は通常1〜2分程度のずれを許容します。このページでは各時刻クレームを読みやすい日時で表示し、トークンが現在有効期間内かどうかも示します。

JWTが適さない場面

JWTは自分自身のクレームを持ち運ぶベアラートークンです。そのため、ステートレスなサービス間通信には非常に向いていますが、セッションの仕組みとしては扱いにくいものです。

扱いにくさの原因は失効です。検証側は鍵以外に何も必要としないため、ストアを参照する場面がなく、exp より前にトークンを無効にする方法がありません。ユーザーをログアウトさせても、パスワードを変更しても、アカウントを無効にしても、すでに発行されたトークンは期限が切れるまで使えます。よくある対策は、ごく短い有効期間とリフレッシュトークンの組み合わせか、リクエストのたびに jti の拒否リストを確認することですが、どちらもステートレスによって取り除くはずだった状態を再び持ち込むことになります。

保存場所も重要です。localStorageにあるトークンはページ上で動くあらゆるスクリプトから読めるため、クロスサイトスクリプティングの欠陥が1つあれば持ち出されます。httpOnly Cookieなら読めませんが、その代わりCSRF対策が必要です。また、トークンはすべてのリクエストでヘッダーに載って送られるため、大きなペイロードには実際のコストがかかります。クレームを詰め込みすぎると、プロキシのヘッダーサイズ上限にぶつかります。

普通のWebセッションなら、サーバー側のストアと対応づけたランダムな不透明IDのほうが、単純で、即座に失効でき、サイズも小さく済みます。

検証が順番にやっていること

正しい検証は一連の手順であり、どれか一つを飛ばしたところに既知の攻撃が潜んでいます。まずアルゴリズムを事前に決め、ヘッダーがそれと食い違うトークンは拒否します。トークンから alg を読み取って信用してはいけません。次に鍵を選びます。共有秘密鍵なら定数ですし、非対称のトークンならヘッダーの kid に対応する公開鍵で、発行者のJWKSエンドポイントから取得してキャッシュしたものです。

3つめに、最初の2つのセグメントをそのままの形で使って署名を計算し直し、定数時間で比較します。4つめに、署名が正しいと確認できてから初めてクレームを読みます。exp と nbf をわずかな許容誤差つきで時計と突き合わせ、iss が期待する発行者であることを確認し、aud に自分が含まれているかを確認します。

設定さえすればほとんどのライブラリはこれらをすべて行いますし、脆弱性の多くはアプリケーションが一部を手作業でやったところから生まれます。検証前にデコードしてクレームを読むのは、デバッガの中なら問題ありませんが、リクエスト処理の中では危険です。その時点のトークンは、誰かがそう主張しているというだけのものだからです。

OAuthとOpenID ConnectにおけるJWTの位置づけ

OpenID Connect では id_token がJWTであり、これはクライアント向けのものです。誰がいつどのようにサインインしたかを伝えます。アクセストークンはAPI向けの別物で、多くのプロバイダーはこれをJWTとして発行しますが、仕様がそれを要求しているわけではありません。APIがイントロスペクションで検証する不透明な文字列でも構いません。

この区別が重要なのは、APIが何をすべきかを左右するからです。アクセストークンが不透明ならAPIはプロバイダーのイントロスペクションエンドポイントを呼びます。JWTならAPIは公開されている鍵を使ってローカルで検証します。id_token をアクセストークンとして扱うのはよくある連携時のバグです。それは別のオーディエンス向けに発行されたものであり、それを受け入れるAPIは、同じプロバイダーが扱うどのアプリケーション向けに発行されたトークンでも受け入れてしまいます。

トークンの中にあるスコープやロールは発行者による主張であって、権限そのものではありません。呼び出し元が何をしてよいかを決めるのは依然としてAPIです。トークンが語るのは、その人が誰であるかと、発行者が何を保証する用意があったかだけです。

繰り返し現れる脆弱性

alg: none という手口はトークンを署名なしと宣言するもので、10年ほど前にはいくつかのライブラリがこれを受け入れていました。アルゴリズム混同はもっと巧妙です。RS256のトークンを用意し、ヘッダーを HS256 に書き換え、発行者の公開鍵をHMACの秘密鍵として署名します。トークンからアルゴリズムを読み取る検証側はこれを受け入れてしまいます。公開鍵は秘密ではないからです。

実務で最も多く見つかるのは弱い共有秘密鍵です。JWTには候補となる秘密鍵をオフラインで試すのに必要なものがすべて含まれており、攻撃者のハードウェアが許す速度で試せます。辞書にある単語なら数秒で破られ、あとは誰でも好きなクレームを入れたトークンを発行できてしまいます。

さらにロジックの誤りがあります。aud のチェックを欠いたために、同じ鍵を共有する別のサービス向けのトークンで認証が通ってしまうもの。入力を検証せずに kid パラメーターでパスから鍵を読み込んでしまうもの。そしてクエリ文字列に入ったトークンを受け付けてしまい、サーバーのログやブラウザの履歴、リファラーヘッダーに残ってしまうものです。

Webアプリケーションにセッションとトークンのどちらを使うか

自前のバックエンドを持つ単一のWebアプリケーションなら、不透明なCookie識別子を使うサーバー側セッションのほうが、ほぼすべての面で単純で優れています。失効は即座に効き、Cookieは小さく、中身はサーバーの外に出ず、署名を誤る余地もありません。代償はセッションストアが必要になることですが、たいていのアプリケーションではテーブル1つかRedisのキー1つで済みます。

JWTが本領を発揮するのは、検証する側が発行者に問い合わせられない場合です。信頼境界をまたぐサービス間呼び出し、サードパーティが使うAPI、往復なしでリクエストを検証しなければならないステートレスなエッジなどです。これらは現実の制約であり、当てはまるなら複雑さを引き受ける価値があります。

多くのチームが落ち着く折衷案は、寿命の短いアクセストークン(時間単位ではなく分単位)と、httpOnly Cookieに入れたうえでサーバー側にも保存して失効できるようにした、寿命の長いリフレッシュトークンの組み合わせです。これなら通常の経路ではステートレスな検証を得つつ、拒否できる場所も残せます。

ブラウザでトークンをどこに置くか

localStorage はそのページで動くどのスクリプトからも読めるため、同一オリジンのどこかにクロスサイトスクリプティングの欠陥が1つあれば、それがサードパーティの解析や広告のスクリプトの中であってもトークンは流出します。多くのチュートリアルではこれが既定になっていますが、選べる選択肢の中では最も弱い方法です。

httpOnly、Secure、SameSite を付けたCookieはスクリプトからまったく読めないため、その種の攻撃はなくなりますが、代わりにCSRF対策が必要になります。ブラウザがCookieを自動的に付けてしまうからです。SameSite=Lax でほとんどは防げ、残りはanti-forgeryトークンで埋めます。

トークンをメモリー上にだけ置く方法、つまりアプリケーション内の変数として持ち、ページ読み込みのたびにCookieで認証するエンドポイント経由で取り直す方法は、よく使われるパターンの中では最も強く、最も不便です。リロードのたびにリフレッシュの往復が必要になるからです。どれを選んでも、トークンはリクエストごとにヘッダーに載って運ばれます。だからペイロードを小さく保つことは、プライバシーだけでなくパフォーマンスの判断でもあります。

よくある質問

デコードできればトークンは正しいと判断できますか?
いいえ。これはJWTで最も重要な点です。ペイロードはbase64urlでエンコードされているだけで暗号化も保護もされていません。誰でも読めますし、正しくデコードできる偽のトークンも作成できます。正しい鍵で署名を検証して初めて、本物で改ざんされていないことが確認できます。
実際のトークンを貼り付けても安全ですか?
デコードも検証もこのページ上のJavaScriptで実行されます。送信・記録・保存は一切行いません。ただしJWTは有効な認証情報です。他の場所で漏えいした可能性がある場合は、貼り付け先に関わらず失効させてください。
なぜHS256とHS512しか検証できないのですか?
これらは共通鍵方式で、署名と検証に同じ秘密鍵を使うため、入力された鍵でローカルに検証できます。RS・ES・PSは公開鍵方式で、通常JWKSエンドポイントから取得する発行者の公開鍵が必要になり、別の処理になります。
exp・iat・nbf とは何ですか?
登録済みクレームで、値はNumericDate(ミリ秒ではなくUNIXエポックからの秒数)です。expは有効期限、iatは発行時刻、nbfは使用可能になる最も早い時刻を表します。本ツールではそれぞれ読みやすい日時で表示します。
alg: none とは何を意味しますか?
署名なしのトークンであることを示します。これを受け入れてしまうライブラリは偽造トークンを通してしまうため、既知の攻撃手法として知られており、検証側は拒否すべきです。本ツールもそのようなトークンを「検証済み」とは表示しません。

最終更新:

参考資料

RFC 7519 — JSON Web Token

最近の変更

  • — 実行できるサンプル、期待される結果、制限事項、参考リンクを追加しました。

問題を報告

操作手順、期待した結果、実際の結果を記載してください。秘密情報や個人情報は削除してください。

関連ツール