ログイン後にサーバが渡す、署名付きのトークン。それがJWTです。仕組みと、安全に使うための注意点を解説します(攻撃手順は載せません)。
仕組み(3つの部分)
JWTは ヘッダ.ペイロード.署名 の3つを . でつないだ文字列です。ヘッダとペイロードはbase64urlでエンコードされているだけ(暗号化ではない)で、署名だけが秘密鍵(または共有鍵)で作られます。
① ヘッダ
署名アルゴリズム等のメタ情報。誰でも読める
② ペイロード
利用者IDや有効期限などの主張。誰でも読める=秘密を入れない
③ 署名
鍵で生成。改ざんされていないことを保証
中身が「読めるだけ」なのが肝心です。だからペイロードはパスワードや個人情報を入れる場所ではありません。JWTの価値は、サーバが署名を検証することで「このトークンは自分が発行し、改ざんされていない」と確認できる点にあります。
安全に使うための防御
署名を必ず検証し、algを固定する(最重要)
サーバ側で署名を毎回検証する。受け入れる署名アルゴリズム(alg)を期待する値に固定し、alg:none(署名なし)は拒否する。トークンのヘッダに書かれたalgを鵜呑みにしない。
JWTのベストプラクティス(RFC 8725)は、ライブラリは呼び出し側が許可アルゴリズムの集合を指定できるようにしなければならず、それ以外は使ってはならないとしています。またJWT中のすべての暗号処理を検証し、1つでも失敗したらJWT全体を拒否しなければならないと定めています。
「alg:none」だけでなく、アルゴリズムの書き換えも防ぐ
alg の悪用は署名を消すことだけではありません。RFC 8725 は、RS256 を HS256 に書き換えられると、一部のライブラリが「RSA公開鍵をHMACの共有鍵として」署名を検証しようとすると説明しています(アルゴリズム混同)。公開鍵は誰でも入手できるので、これが通ると攻撃者が自分で有効な署名を作れてしまいます。
防ぎ方は同じです。ヘッダの alg を信用せず、サーバ側が期待するアルゴリズムを固定する。鍵の種類ごとに検証関数を選ぶのではなく、アルゴリズムを先に決めてから検証するのが正しい順番です。
「誰宛て・いつまで」まで検証する
署名が正しいことは「本物である」ことしか示しません。それが自分宛てで、まだ有効かは別の検証です。
RFC 7519 は aud(宛先)について「受け取った側が aud の値に自分を認めないなら、そのJWTは拒否しなければならない」、exp について「その時刻以降は処理のために受け入れてはならない」と定めています(nbf も同様に「その時刻より前は受け入れてはならない」)。RFC 8725 はさらに、発行者(iss)と鍵の対応を検証することを求めています。別サービス向けに正しく発行されたトークンが自分のところで通ってしまう事故は、この検証を飛ばしたときに起きます。
ペイロードに秘密を入れない
中身は誰でもデコードできる前提。パスワード・個人情報・APIキーなどの秘密は入れない。入れるのは識別子や権限など、漏れても致命的でない主張だけにする。
有効期限を短くし、失効の手段を持つ
アクセス用トークンは短命にする。長すぎる有効期限は、盗まれたときの被害時間を延ばす。失効が必要な設計なら、短命アクセストークン+サーバ側で管理するリフレッシュ/セッションを併用する。
強い鍵を使い、盗まれない場所に置く
署名鍵は十分に強く、使い回さず、サーバ側の安全な場所に保管する。トークン自体も、XSSで盗まれない・安全な属性のCookieで運ぶなど、保管と運搬を含めて守る(→ XSS 経由の窃取に注意)。
当サイトの視点:JWTは、どんな用途にも使えるセッション管理ではない
JWTはよく「ログイン状態の保存」に使われますが、失効させにくいという弱点があります。署名が正しく期限内なら、サーバは基本的にそれを信じるため、ログアウトや無効化を即時に効かせにくいのです。
当サイトの立場は、JWTを有効期限の長いセッション代わりに使わないことです。短命のアクセストークンと、サーバ側で取り消せるリフレッシュ/セッションを組み合わせれば、JWTの利点(検証が軽い)と失効のしやすさを両立できます。
注意:「デコードできた」は「正しい」ではない
JWTは中身が読めるので、JWTデコーダに貼れば誰でもヘッダとペイロードを確認できます。ただしデコード(中身を読む)と検証(本物か確かめる)はまったくの別物です。デコードできることは正しさを何も保証しません。トークンが本物かを決めるのは、必ずサーバ側の署名検証です。デバッグで中身を見るのは便利ですが、「読めたから有効」と勘違いしないでください。
セッション(Cookie)との違い
JWTは「ログイン状態を持つ方法」の一つにすぎず、サーバー側のセッションと比べて向き不向きがあります。
| 項目 | サーバー側のセッション(Cookie) | JWT |
|---|---|---|
| 状態の置き場所 | サーバー(DBやメモリ)。Cookieには識別子だけ | トークンの中。サーバーは署名を検証するだけ |
| ログアウト・停止 | サーバーで消せばすぐ効く | 有効期限まで通る。取り消す仕組みを別に作る |
| 中身の見え方 | 識別子だけなので中身は見えない | 誰でもデコードして読める |
| 向く場面 | 同じドメインの普通のWebアプリ | 複数のサービスやAPIが同じトークンを検証する場面 |
同じドメインで完結するWebアプリなら、サーバー側のセッションで足りることが多くあります。JWTを選ぶ場合も、ログイン時に新しいトークンを発行する点はセッション固定への対策と同じです。
具体例:アルゴリズムを確かめないライブラリの脆弱性
アルゴリズムの書き換えは、理論上の話ではありません。Node.jsで広く使われるJWTライブラリ jsonwebtoken の 4.2.1 以前には、トークンのヘッダに書かれたアルゴリズムを十分に確かめないため、検証を回避できる脆弱性がありました(CVE-2015-9235、4.2.2 で修正)。公開鍵で検証するはずのトークンを、共有鍵(HS系)のアルゴリズムで署名して送ると検証を通ってしまうというもので、上で説明したアルゴリズム混同そのものです。
教訓は2つあります。
- ライブラリを最新に保つ。依存ライブラリに既知の脆弱性がないかは、依存パッケージ脆弱性スキャナに lockfile を貼って確かめられる
- ライブラリの既定に任せず、受け入れるアルゴリズムを自分のコードで明示する
自分のトークンで確認する方法
自分のサービスが発行したトークンをJWTデコーダに貼り、次の点を見ます。デコードはブラウザ内で行われ、トークンはサーバーに送られません。それでも本番の有効なトークンは、確認後にログアウトして無効にしておくと安心です。
- ヘッダの
algが想定どおりか(noneになっていないか) exp(有効期限)があり、発行時刻(iat)からの長さが用途に合っているかaud・issが入っていて、自分のサービスを指しているか- ペイロードにメールアドレスや電話番号、APIキーなど、見られて困る値が入っていないか
サーバー側の検証は、開発環境のテストで確かめます。署名を1文字変えたトークン、alg を変えたトークン、期限切れのトークン、別のサービス向け(aud 違い)のトークンが、すべて拒否されることを自動テストにしておきます。
出典(一次情報)
- IETF RFC 8725「JSON Web Token Best Current Practices」 — datatracker.ietf.org(アルゴリズムの固定・すべての暗号処理の検証・アルゴリズム混同・発行者と鍵の対応)
- IETF RFC 7519「JSON Web Token (JWT)」 — datatracker.ietf.org(
exp/nbf/audの規範的な定義) - GitHub Advisory Database「Verification Bypass in jsonwebtoken」(CVE-2015-9235) — github.com/advisories(4.2.1 以前が対象、4.2.2 で修正)
- OWASP HTML5 Security Cheat Sheet — cheatsheetseries.owasp.org(セッションの識別子を localStorage に置かない)
次に読む
- ツール:JWTデコーダ / 検査(中身の確認・alg:noneや期限切れの点検)
- 設計:パスワードリセットの設計欠陥(トークンを発行する側の設計ミス)
- 用語:XSS とは(トークン窃取の典型経路)/ CSRF とは / セッション固定とは / 公開鍵暗号とは
- 入門:二要素認証(MFA)の選び方