JWTはあらゆる場所にある。Webアプリにログインするたびに、APIはJSON Web Tokenを発行し、そのトークンは身元、権限、しばしばペイロードに個人情報の一部を運ぶ。フォーマット自体は10年間安定していたが、2024-2025年に活動が活発化した — RFC 8725(JWTの現在のベストプラクティス)が新仕様の必須要件になり、「JWTは悪い」という議論がセッションサイズをめぐって再燃し、複数の大手プロバイダーがブラウザ使用として不透明トークンに移行した。その結果、以前よりも多くの開発者がトークンを手動で検査している。何か問題が起きたとき — セッションが間違った時間に期限切れになる、有効だと思うトークンがAPIに拒否される、チームメンバーが「JWTの中身は?」と言う — ほとんどの開発者が最初にするのは、トークンをオンラインデコーダーに貼り付けることだ。
問題は、それらのデコーダーのほとんどがトークンをサーバーに送信するということだ。トークン自体はシークレットではない(base64エンコードされたJSONだ)が、ペイロードの内容はしばしばシークレットだ:ユーザーID、メールアドレス、場合によってはセッションID、場合によっては権限リスト。サービスがJWTを他の内部サービスとの通信に使う場合、ペイロードには内部サービス名や機能フラグが含まれることもある。
サードパーティデコーダーに貼り付けられたトークンは、小さな情報漏洩だ。ほとんどの場合、問題はない。しかし原則は重要だ:信頼できないインフラストラクチャを信頼せずにトークンを検査できるべきだ。
JWTの実際の見た目
JWTは3つのbase64urlエンコード文字列で、ドットで区切られている:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJBbGljZSIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
3つの部分はヘッダー、ペイロード、署名だ。最初の2つをデコードするとJSONになる:
// ヘッダー
{
"alg": "HS256",
"typ": "JWT"
}
// ペイロード
{
"sub": "12345",
"name": "Alice",
"iat": 1516239022
}
署名は、トークンが秘密を知っている者によって発行されたことを証明する暗号ハッシュだ。誰でもペイロードをデコードできる — それはセキュリティの境界ではない。セキュリティの境界は、署名を検証できるかどうかだ。
検証とデコードの違い
ここで多くの開発者が間違える。デコードは検証ではない。 デコーダーはJSONをbase64デコードして読めるようにするだけだ。誰でもでき、トークンが本物であることは証明しない。検証は、知られた秘密に対して署名をチェックし、トークンが改ざんされていないことを確認する行為だ。
- トークンの内容をデバッグする場合(どのクレームがあるか、いつ期限切れになるか)、デコードで十分だ。
- ユーザーを認証する場合、検証が必要で、信頼できるサーバーで行われなければならない。
ほとんどのオンラインデコーダーツールはデコードのみだ。ヘッダーとペイロードを表示するが、秘密を求めることがない — 検証しておらず、トークンの中身を表示しているだけだからだ。
ローカルでデコードする方法
サードパーティツールは不要だ。JWTは単に3つのbase64url文字列だ。ターミナルを開く:
echo 'eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJBbGljZSIsImlhdCI6MTUxNjIzOTAyMn0' | tr '_-' '/+' | base64 -d
これがペイロードだ。ヘッダーは最初のセグメントで、同じトリックだ。
tr '_-' '/+'を暗記したくない場合、DevSpeedToolsのJWTデコーダーがブラウザで同じことをする。ネットワークリクエストなし、アップロードなし、ログなし。DevTools → Networkを開き、トークンを貼り付けて、何もマシンから出ることを自分で確認しよう。
検証が必要な場合
認証問題をデバッグしていて、トークンが有効かどうか(中身だけでなく)を知る必要がある場合、署名を検証する必要がある。検証は、認証サーバーが使うのと同じ秘密鍵または公開鍵で行われなければならない。つまり、あなたが制御するインフラストラクチャで行われなければならない。
秘密をウェブサイトに貼り付けることに関連しない検証のオプション:
- Node.jsで:
jsonwebtoken.verify(token, process.env.JWT_SECRET)。 - Pythonで:
jwt.decode(token, key, algorithms=['HS256'])。 - Goで:
token, err := jwt.Parse(tokenString, keyFunc)。 - CLI:
jwt-cli(Rust)または1行コマンドのpyjwt。
何らかの理由でオンライン検証ツールが必要な場合、ブラウザ内で実行されることを明示するものを探す(ソースは通常公開されている)。誠実なツールはそう言っている。それ以外はトークンをサーバーにアップロードすると仮定すべきだ。
「invalid signature」エラー
開発者がデコーダーにたどり着く一般的な理由はinvalid signatureエラーだ。このメッセージは2つのことを伝えている:
- トークンが、検証に使っているのとは異なる秘密で署名されていること。
- トークンが改ざんされた可能性があること。
あるいは — そしてこれが多くの人が見落とすもの — トークンが期限切れの可能性がある。 ペイロードのexpクレームを確認しよう。現在のUnixタイムスタンプがexpを過ぎていたら、トークンは無効だ。一部のライブラリは期限切れトークンに対してinvalid signatureを返すが、これは誤解を招く。
JWTデコーダーはexp値をペイロードの他の部分とともに表示するので、期限切れトークンを一目で確認できる。nbf(not before)、iat(issued at)、aud(audience)も同様で、デバッグに役立つ。
まとめ
JWTデコーダーは便利だ。しかし、開発者が本番トークンの貼り付けに無頓着になる場所でもある。より安全なデフォルト:
- クイックインスペクションにはローカルまたはブラウザ内デコーダーを使う。 アップロードなし、漏洩なし。
- 検証にはCLIまたは自分のコードを使う。 決して秘密をウェブサイトに貼り付けない。
- 最初に標準クレームを確認する —
exp期限切れが「トークンが突然動かなくなった」問題の原因の大多数だ。
トークンはシークレットではない。トークンの内容はしばしばシークレットだ。それに応じて扱おう。