コンテンツへスキップ
Toova
すべてのツール

JWT とは何か、安全にデコードする方法

Toova

JSON Web Token は現代のウェブ開発のあらゆる場所に現れます — 認証ヘッダー、OAuth フロー、API キー、セッション管理。Bearer トークンを必要とする任意の API で作業したことがあれば、すでに JWT を使用しています。しかし、ほとんどの開発者はそれらを不透明なブロブとして扱い、めったに中を見ません。JWT の構造、各部分が何を意味するか、そしてセキュリティの間違いを犯さずにデコードする方法を理解するには約 15 分かかります。このガイドはそれをすべてカバーします。

JWT の構造

すべての JWT は、ドットで区切られた 3 つの Base64URL エンコードされたセグメントの文字列です:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImlzcyI6Imh0dHBzOi8vYXV0aC5leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHBzOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzQ3MDAwMDAwLCJpYXQiOjE3NDY5OTY0MDAsInNjb3BlIjoicmVhZDp1c2VycyJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

形式はヘッダー.ペイロード.署名です。各セグメントは異なる情報をエンコードします。

ヘッダー

最初のセグメントはヘッダーです。上記の例をデコードすると、次のようになります:

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

alg はトークンの署名に使用されるアルゴリズムを指定します — RS256 は RSA と SHA-256 を意味します。typ はトークンタイプを識別します。これら 2 つのフィールドは最小限です。一部のトークンには、複数の鍵がローテーションされているときに検証者にどの公開鍵を使用するかを伝える kid (鍵 ID) も含まれます。

ペイロード

2 番目のセグメントはペイロード — 実際のデータです。デコード後:

{
  "sub": "user_123",
  "iss": "https://auth.example.com",
  "aud": "https://api.example.com",
  "exp": 1747000000,
  "iat": 1746996400,
  "scope": "read:users"
}

ペイロードにはクレームが含まれます: トークンが表すエンティティに関する声明です。一部のクレームは標準化されています (登録済みクレームと呼ばれます)。他はアプリケーション固有 (プライベートクレームと呼ばれます) です。ペイロードは Base64URL エンコードされており、暗号化されていません。トークン文字列を取得した人は誰でも、その中のすべてのクレームを読むことができます。

署名

3 番目のセグメントは署名です。エンコードされたヘッダーとペイロードを取得し、ドットで連結し、alg で指定された秘密鍵または秘密鍵で結果に署名することで生成されます:

signature = sign(
  base64url(header) + "." + base64url(payload),
  secretOrPrivateKey
)

署名により、対応する公開鍵 (対称アルゴリズムの場合は秘密鍵) を持つ任意の関係者が、トークンが信頼できるソースによって発行され、ヘッダーやペイロードが発行後に変更されていないことを検証できます。ペイロード内の単一の変更された文字が、署名を完全に無効にします。

JWT をデコードする方法

デコードは検証とは異なります。デコードは単にトークン内のデータを読み取ります。検証は、トークンが本物で期限切れでないことを確認します。本番コードでは常に検証する必要があります。デコードのみはデバッグに有用です。

ステップ 1: トークンを分割

JWT 文字列を . 文字で分割します。3 つのセグメントを取得します。3 つ未満の場合、トークンは不正な形式です。

ステップ 2: ヘッダーとペイロードをデコード

最初の 2 つのセグメントのそれぞれを Base64URL デコードします。Base64URL は標準 Base64 に似ていますが、+ の代わりに - を、/ の代わりに _ を使用し、パディング文字はありません。デコードされたら、各セグメントを JSON として解析できます。

ステップ 3: 解析して検査

クレームを読みます。現在の Unix タイムスタンプに対して exp (有効期限) をチェックします。issaud を見て、トークンがあなたのアプリケーション用であることを確認します。署名を検証するまで、どのクレームも信頼しないでください。

開発中の素早い検査には、JWT Decoder を使用します。これはブラウザ内で完全に動作します — トークンはデバイスから出ません。生のバイトを見たい場合は、Base64 エンコーダー/デコーダーで Base64 セグメントを手動でデコードすることもできます。テストスクリプトで署名を検証するには、HMAC ジェネレーターでコードを書かずに HS256 署名を再現できます。

ステップ 4: コードで署名を検証

本番では、常にあなたの言語用に構築されたライブラリを使って署名を検証してください。手動で署名検証を実装しないでください。人気のオプションには、Node.js の josejsonwebtoken、Python の PyJWT、Go の golang-jwt/jwt、Java の nimbus-jose-jwt があります。

常にアルゴリズムを明示的に渡してください。ライブラリにトークンヘッダーからアルゴリズムを推測させないでください。

標準クレーム (登録済みクレーム)

RFC 7519 は、一連の登録済みクレーム名を定義します。これらは必須ではありませんが、存在する場合は標準のセマンティクスに従う必要があります。

iss — 発行者

トークンを作成および署名したエンティティ。通常、認可サーバーを識別する URL です (たとえば、https://auth.example.com)。検証者は、iss が期待値と一致することを確認し、未知の発行者からのトークンを拒否すべきです。

sub — 主題

トークンが表すプリンシパル — 通常、ユーザー ID、サービスアカウント名、またはデバイス識別子。値は発行者のコンテキスト内で一意でなければなりません。アプリケーションは sub を使用して、リクエストが属するユーザーを識別します。

aud — 対象者

トークンの意図された受信者。API の識別子が https://api.example.com の場合、他のサービス用に発行されたトークンは拒否されるべきです。aud を検証しないと、攻撃者があるサービスから取得したトークンを別のサービスで再利用できます。

exp — 有効期限

トークンが受け入れられてはならない Unix タイムスタンプ。常に exp を検証してください。有効期限のないトークンは事実上永遠に存続します — 盗まれた場合、攻撃者は無期限のアクセスを持ちます。

iat — 発行時刻

トークンが作成されたときの Unix タイムスタンプ。技術的に期限切れでないが疑わしく古いトークンを検出するのに有用です。iat を使用して、exp とは独立して最大トークン年齢を強制できます。

nbf — Not Before

トークンが受け入れられてはならない Unix タイムスタンプ。exp よりも一般的ではありませんが、トークンを事前に発行するときに有用です — たとえば、特定の時刻まで開始してはならないスケジュールされたタスク。

jti — JWT ID

トークンの一意の識別子。発行者が使用済みの jti 値を保存し、重複した ID を持つトークンを拒否することで、トークンリプレイ攻撃を防ぐことができます。パスワードリセットリンクなど、ワンタイム使用トークンが必要なときに必要です。

セキュリティの落とし穴

JWT 実装には微妙な脆弱性の歴史があります。JWT を受け入れるコードを出荷する前に理解すべき最も重要なものは次のとおりです。

「alg: none」攻撃

標準の初期からの一部の JWT ライブラリは、ヘッダーに "alg": "none" を指定するトークンを受け入れます。攻撃者は任意の有効なトークンを取り、アルゴリズムを none に置き換え、署名を取り除き、ライブラリはそれを完全に有効として受け入れます — 署名は不要です。

修正: 検証関数を呼び出すときに、期待するアルゴリズムを常に明示的に指定します。alg: none を主張する任意のトークンを無効として扱います。ほとんどの現代のライブラリはこれに対処していますが、本番に行く前にライブラリのデフォルトを確認する価値があります。

アルゴリズム混乱 (RS256 から HS256 へのダウングレード)

両方のアルゴリズムをサポートする一部のライブラリは、トークンヘッダーの alg フィールドを使用して検証方法を決定します。攻撃者はアルゴリズムを HS256 に変更し、サーバーの公開鍵を HMAC 秘密として使用してトークンに署名できます。サーバーは HS256 を見て、公開鍵に対して検証し、偽造されたトークンを受け入れます。

修正は同じです: 検証コードでアルゴリズムをロックします。トークンヘッダーが検証に使用されるアルゴリズムに影響しないようにします。

期限切れのトークンを受け入れる

exp を検証しないことは一般的な見落としです — 際限なく成長する猶予期間を開発者が追加するとき、または検証コードパスがコードブランチでバイパスされるときに導入されることがあります。期限切れのトークンを欠落しているトークンと同じに扱います: 401 で拒否し、クライアントに再認証またはリフレッシュトークンの使用を要求します。

ペイロード内の機密データ

ペイロードは Base64URL エンコードされており、暗号化されていません。トークンを傍受した人は誰でもそれをデコードできます。パスワード、クレジットカード番号、社会保障番号、または他の機密データをペイロードに入れないでください。機密クレームを安全に送信する必要がある場合は、ペイロードを暗号化する JWE (JSON Web Encryption) トークンを使用します。JWS (一般的なケース) で署名された JWT は、機密性ではなく真正性のみを保証します。

対象者検証の欠落

aud 検証をスキップすると、サービス A 用に発行されたトークンがサービス B で再生される可能性があります — 両方のサービスが同じ署名鍵を共有するか、同じ発行者を信頼している限り。マルチサービスアーキテクチャでは、常にトークンの対象者がサービスの識別子と一致することを検証します。

2026 年のベストプラクティス

ほとんどのアプリケーションには RS256 を選択

RS256 は署名に秘密鍵を、検証に公開鍵を使用します。認可サーバーのみが秘密鍵を保持します。任意のサービスは公開鍵を使ってトークンを検証でき、公開的に公開できます (しばしば JWKS エンドポイント経由)。個々のサービスが侵害されても、攻撃者はトークンを検証する能力を獲得しますが — 新しいものを偽造することはできません。

HS256 は署名と検証の両方に単一の共有秘密を使用します。トークンを検証できる任意のサービスは、それらを作成することもできます。マイクロサービスのセットアップでは、多くのサービスにわたって秘密を共有すると、侵害の被害範囲が増加します。

アクセストークンを短命に保つ

JWT には組み込みの取り消しメカニズムはありません — 発行されたトークンは、期限切れになるまで有効です (ブロックリストを実装しない限り、それはサーバー側の状態を再導入します)。短い有効期限 (15 分から 1 時間) は、トークンが盗まれた場合の損害ウィンドウを制限します。アクセストークンを localStorage ではなく安全な HttpOnly Cookie に保存された長命のリフレッシュトークンと組み合わせます。

リフレッシュトークンローテーションを使用

クライアントがリフレッシュトークンを使用して新しいアクセストークンを取得するときは、新しいリフレッシュトークンを発行し、古いものを無効にします。こうすると、盗まれたリフレッシュトークンは、合法的なクライアントが元のものを次に使用しようとしたときに検出されます: サーバーは再利用されたリフレッシュトークンを見て、セッション全体を取り消すことができます。このパターンは RFC 6819 で説明されており、現代の認可サーバーで広くサポートされています。

JWKS エンドポイントを公開

JSON Web Key Set (JWKS) エンドポイント (慣習的に /.well-known/jwks.json) は、署名公開鍵を標準形式で公開します。他のサービスは、帯域外で鍵を与えられることなく、JWKS をフェッチしてトークンを検証できます。これにより、鍵のローテーションも簡単になります: JWKS に新しい鍵を追加し、それで署名を開始し、すべての既存のトークンが期限切れになったら古い鍵を削除します。

鍵を定期的にローテーション

秘密鍵が侵害されない場合でも、鍵を定期的にローテーションすると、盗まれた鍵がトークンを偽造するために使用できるウィンドウが制限されます。JWT ヘッダーで kid を使用して署名鍵を参照し、検証者がローテーション中に既存のトークンを壊さずに JWKS から正しい公開鍵を選択できるようにします。

すべてのクレームを検証

すべてのリクエストで issaudexpnbf をチェックします。セットアップで冗長に見えるからといって、これらのいずれかをスキップしないでください — 今日それらが不必要に見える条件は、インシデント中に変化する条件と正にそれです。

JWT vs セッション Cookie

JWT とサーバーサイドセッションは同じ問題を解決します — ステートレス HTTP 全体で認証状態を永続化する — が、異なるトレードオフがあります。

JWT は自己完結型です。サーバーはリクエストを検証するためにデータベースをクエリする必要はありません。これにより、分散システムとモバイルやサードパーティクライアントによって消費される API にとって魅力的になります。欠点は、取り消しにはブロックリスト (サーバー側の状態を再導入) または侵害されたトークンの残りの寿命を許容することが必要なことです。

セッション Cookie はサーバー検証されます。サーバーはセッション状態を保存し、セッションを削除することでアクセスを即座に取り消すことができます。HttpOnlySecure フラグを持つ Cookie は、JavaScript アクセスとネットワーク傍受から保護されます。欠点は、すべてのリクエストがデータベースルックアップを必要とし、スケールでボトルネックになる可能性があることです。

サーバーレンダリングされたページと単一のバックエンドを持つ伝統的なウェブアプリケーションには、セッション Cookie が依然として堅実でよりシンプルな選択肢のままです。複数のクライアントによって消費される API、マイクロサービスアーキテクチャ、モバイルアプリには、短い有効期限とリフレッシュトークンローテーションを持つ JWT がより実用的なオプションです。

トークンをデコードする

JWT を検査する最速の方法は、それを JWT Decoder に貼り付けることです。トークンを分割し、ヘッダーとペイロードをデコードし、クレームを読みやすい形式でレンダリングします — すべてトークンをどこにも送信せずに。生の Base64URL 文字列を手動でエンコードまたはデコードする必要がある場合、Base64 エンコーダー/デコーダーが標準と URL セーフのバリアントの両方を処理します。テストスクリプトで HMAC 署名を検証するには、HMAC ジェネレーターで HS256 署名を再現し、トークンに含まれるものと比較できます。

完全な仕様については、RFC 7519 (JWT) と jwt.io のインタラクティブな例を参照してください。