JWTトークンを安全にデコードする方法
JWT(JSON Web Token)は、RFC 7519で定義されたコンパクトでURLセーフなトークン形式です。パーティ間の認証と情報交換に使用されます。JWTは、現代のWebアプリケーションにおいてクライアントとサーバー間でクレームを安全に表現するための事実上の標準となっています。JWTは自己完結型であり、リクエストの認証に必要なすべての情報がトークン自体に直接埋め込まれているため、多くのアーキテクチャでサーバー側のセッションストレージが不要になります。
JWTの人気は、シングルページアプリケーション、モバイルアプリ、APIゲートウェイ、マイクロサービス間通信に及びます。ユーザーがアプリケーションにログインすると、サーバーはJWTを発行し、クライアントはそれを保存して後続のリクエストごとに提示します。サーバーはトークンの署名を検証してリクエストを認証および認可します。JWTを安全にデコードして検査する方法を理解することは、認証フローのデバッグ、クレームの検証、機密データを公開したり脆弱性を導入したりすることなくトークン関連の問題をトラブルシューティングする必要がある開発者にとって重要です。
JWT構造の理解
JWTはドットで区切られた3つの部分で構成され、各部分はBase64URL(Base64エンコーディングのURLセーフバリアント)を使用してエンコードされています:
header.payload.signature
例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwi
bmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4
fwpMeJf36POk6yJV_adQssw5c
最初のセグメントはヘッダーで、署名アルゴリズムやトークンタイプなど、トークンに関するメタデータが含まれています。2番目のセグメントはペイロードで、行われているクレームまたはアサーションを運びます。3番目のセグメントは署名で、エンコードされたヘッダーとペイロードをドットで結合し、ヘッダーで指定されたアルゴリズムと秘密鍵または秘密鍵を使用して結果に署名することによって計算されます。
Base64URLエンコーディングの詳細
JWTはBase64URLエンコーディングを使用します。これは標準のBase64と2つの重要な点で異なります。第一に、+文字を-に置き換えて、+がスペースとして解釈されるURLクエリパラメータでの問題を回避します。第二に、/文字を_に置き換えて、URLパスセパレータとの競合を回避します。さらに、パディング文字(=)はJWTセグメントの末尾から削除されます。これはデコードに不要であり、トークンに不要なバイトを追加するだけだからです。
JWTヘッダークレーム
ヘッダーには通常、アルゴリズムとトークンタイプが含まれますが、高度なシナリオでは追加のメタデータを含めることもできます。
| クレーム | 名前 | 値の例 |
|---|---|---|
| alg | アルゴリズム | HS256, RS256, ES256 |
| typ | トークンタイプ | JWT |
| kid | キーID | unique-key-id |
| jku | JWKセットURL | https://example.com/.well-known/jwks.json |
kid(キーID)クレームは、複数の署名鍵がローテーションされている環境で特に重要です。これにより、サーバーは推測することなくキーセットから正しい鍵を検索できます。jkuクレームは、JSON Web Key Set(JWKS)を含むURLを指し、検証者はこれをフェッチして署名検証の公開鍵を取得できます。ただし、信頼できないトークンで指定されたURLから鍵をフェッチすることはセキュリティリスクをもたらすため、適切な検証を行った上でのみ行うべきです。
一般的なJWTペイロードクレーム
ペイロードには、ユーザーとトークン自体に関する情報を提供するクレームが含まれています。これらのクレームは、登録済みクレーム、公開クレーム、非公開クレームに分類されます。
| クレーム | 名前 | 説明 |
|---|---|---|
| sub | サブジェクト | ユーザー識別子 |
| iss | 発行者 | トークンを発行した者 |
| aud | オーディエンス | 意図された受信者 |
| exp | 有効期限 | トークンの有効期限タイムスタンプ |
| nbf | 有効開始日時 | トークンが有効になる日時 |
| iat | 発行日時 | トークン作成時刻 |
| jti | JWT ID | 一意のトークン識別子 |
登録済みクレームはJWT仕様によって標準化されており、特定の意味を持ちます。expクレームはセキュリティにとって特に重要です。トークンに有効期限が含まれていない場合、またはサーバーがそれを検証できない場合、トークンは無期限に有効なままであり、トークンが漏洩した場合に深刻な脆弱性が生じます。iatクレームはトークンが作成された時刻を記録し、トークンの経過時間の決定やトークンローテーションポリシーの実装に使用できます。
これらの登録済みクレームに加えて、アプリケーションは通常、ユーザーロール、権限、メールアドレス、その他のアプリケーション固有のデータのカスタムクレームを含めます。例えば、トークンには"role": "admin"や"permissions": ["read", "write", "delete"]が含まれる場合があります。これらのカスタムクレームは注意深く検査する必要がありますが、署名の検証なしに信頼してはいけません。
デコードと検証の違い
デコードと検証の区別は、JWTを扱う際に理解すべき最も重要な概念です。この2つを混同することは、セキュリティ脆弱性の一般的な原因です。
デコードは、単にヘッダーとペイロードをBase64URLデコードします。誰でも秘密鍵なしでJWTをデコードできます。デコーダーツールにJWTを貼り付けるか、最初の2つのセグメントで単純なBase64URLデコード関数を実行することで、任意のJWTをデコードできます。これにより、JWTは検査とデバッグに有用ですが、ペイロードの内容が公開されて読めることも意味します。
検証は、秘密鍵(HMAC)または公開鍵(RSA/ECDSA)を使用して署名をチェックします。これにより、トークンが改ざんされておらず、信頼できる当事者によって発行されたことが確認されます。検証はセキュリティを提供するステップです。検証がない場合、攻撃者はペイロードを変更してユーザーIDやロールを変更し、ヘッダーとペイロードを再エンコードし、検証をスキップしたり不適切に実装したりするサーバーに対して有効に見える新しい署名を作成できます。
アルゴリズム混乱攻撃
最も危険なJWT脆弱性の1つは、アルゴリズム混乱攻撃(キー混乱攻撃とも呼ばれます)です。この攻撃は、アルゴリズムがヘッダーで指定されているという事実を悪用します。ヘッダーはBase64URLエンコードされているだけで暗号化されていないため、攻撃者が制御できます。
典型的なシナリオでは、サーバーはトークンの署名にRS256(RSA with SHA-256)を使用します。サーバーの公開鍵は広く利用可能ですが、秘密鍵は秘密に保たれます。攻撃者は有効なトークンを傍受し、algヘッダーをRS256からHS256に変更します。HS256は対称秘密鍵を使用するため、サーバーはHS256アルゴリズムを使用して署名の検証を試みます。重要な間違いは、サーバーがRS256の公開鍵をHS256の秘密として使用する場合です。公開鍵は公開されているため、攻撃者は公開鍵をHMAC秘密として使用して、任意のペイロードに対して有効なHS256署名を計算できます。サーバーは同じ公開鍵を使用して署名を検証し、偽造されたトークンを受け入れます。
この攻撃を防ぐには、サーバーは検証前にアルゴリズムが期待されるアルゴリズムと一致することを検証するか、設計上この攻撃に影響を受けないライブラリを使用する必要があります。多くの最新のJWTライブラリは、このリスクを排除するために、非対称検証と対称検証に別々のメソッドを使用しています。
安全なデコード手順
JWTトークンをデコードして検査する必要がある場合は、毎回次の手順に従ってください。
-
アプリケーションからJWTトークンをコピーします。ドットで区切られた3つのセグメントすべてを含む完全なトークンを取得したことを確認してください。部分的なトークンは適切にデコードできません。
-
ブラウザまたはローカルマシン上で完全に実行されるJWTデコーダーのような信頼できるデコーダーツールを使用します。トークンをログに記録したりリモートサーバーに送信したりする可能性のある未知のオンラインツールにトークンを貼り付けないでください。トークンの有効期間が短くても、第三者に公開すると、その有効期間内に再生される可能性があります。
-
ヘッダーを検査してアルゴリズムを確認します。アルゴリズムがサーバーで使用を期待しているものと一致することを確認します。
noneなどの予期しないアルゴリズムや、RS256からHS256への切り替えが見られる場合は、トークンを疑わしいものとして扱います。 -
ペイロードクレームを確認します。
exp(有効期限)およびnbf(有効開始日時)タイムスタンプをチェックして、トークンが現在有効であることを確認します。iss(発行者)がサーバーの識別子と一致することを確認します。aud(オーディエンス)クレームが存在する場合、アプリケーションが含まれていることを確認します。 -
トークンを公開しないでください。バグレポート、Stack Overflowの質問、GitHub Gistに投稿されたトークンは、有効期限が切れる前に見つけた誰にでも悪用される可能性があります。共有する前に、必ずトークンを編集するか、ダミーの例に置き換えてください。
-
信頼できない鍵で検証しないでください。検証コードがユーザー入力、リモートURL、攻撃者によって変更可能な設定ファイルから鍵を受け入れる場合、認証システム全体が危険にさらされます。
コードでのJWTデコード
オンラインツールはアドホックな検査に便利ですが、アプリケーションでプログラムによってJWTをデコードする必要があることがよくあります。以下は、署名検証を実行せずに、いくつかの人気プログラミング言語でJWTペイロードをデコードする方法です。
JavaScript(Node.js)
function decodeJWT(token) {
const payload = token.split('.')[1];
const decoded = Buffer.from(payload, 'base64url').toString('utf8');
return JSON.parse(decoded);
}
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
console.log(decodeJWT(token));
Python
import base64
import json
def decode_jwt(token):
payload = token.split('.')[1]
# Add padding for base64 decoding
padding = 4 - len(payload) % 4
if padding != 4:
payload += '=' * padding
decoded = base64.urlsafe_b64decode(payload)
return json.loads(decoded)
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
print(decode_jwt(token))
これらの例は、検査目的のみのデコードを示しています。本番環境での検証には、アルゴリズム検証、鍵管理、署名検証を正しく処理する、よく監査されたJWTライブラリを常に使用してください。
セキュリティのベストプラクティス
安全なデコードに加えて、以下のベストプラクティスに従うことでJWT実装のセキュリティを維持できます。
短い有効期限を使用する。 アクセストークンは15分から60分以内に期限切れになるようにしてください。有効期間の短いトークンは、トークンが漏洩した場合の損害を制限します。アクセストークンを、より長い寿命を持ち個別に失効できるリフレッシュトークンと組み合わせて使用します。
必要なクレームをすべて検証する。 検証時には常にexp、nbf、iss、audクレームをチェックしてください。多くの攻撃は、クレーム検証の欠落または不正確さを悪用します。
分散システムでは非対称アルゴリズムを使用する。 RS256またはES256を使用すると、サーバーは秘密鍵でトークンに署名し、任意のサービスが公開鍵で検証できます。これにより、サービス間で秘密を共有する必要がなくなります。
トークンブラックリストを実装する。 トークンを期限切れ前に失効させる必要があるシナリオでは、ブラックリストを維持するか、データベースに保存されたトークンバージョン番号を使用します。パスワード変更時やアカウント停止時にバージョンをインクリメントして、既存のすべてのトークンを無効にします。
セキュリティ警告
信頼できないソースからのトークンを決して受け入れないでください。ペイロードを信頼する前に、必ずサーバーで署名を検証してください。攻撃者がRS256をHS256に変更して公開鍵を秘密として使用するアルゴリズム混乱攻撃に注意してください。署名検証を安全に実装する評判の良いJWTライブラリを使用し、最新のセキュリティパッチの恩恵を受けるためにそれらを最新の状態に保ってください。
デコードは検証ではないことを忘れないでください。誰でもペイロードをBase64デコードするだけでJWTの内容を読むことができます。機密性に関してはJWTペイロードを公開情報として扱い、整合性と信頼性については署名検証に依存してください。