用語辞典11分で読める

JWT(JSON Web Token)とは:署名付きトークンの仕組みと安全な使い方

JWT(JSON Web Token)は、サーバが署名して発行する、改ざんを検出できるトークンです。仕組み(ヘッダ・ペイロード・署名の3部)と、よくある危険(署名を検証しない・alg:noneを許す・中身に秘密を入れる・有効期限が長すぎる)への防御を、攻撃手順を伏せて防御目線で解説します。中身の確認はJWTデコーダで。

本文を読む
公開日
更新日
目次

ログイン後にサーバが渡す、署名付きのトークン。それがJWTです。仕組みと、安全に使うための注意点を解説します(攻撃手順は載せません)。

仕組み(3つの部分)

JWTは ヘッダ.ペイロード.署名 の3つを . でつないだ文字列です。ヘッダとペイロードはbase64urlでエンコードされているだけ(暗号化ではない)で、署名だけが秘密鍵(または共有鍵)で作られます。

① ヘッダ

署名アルゴリズム等のメタ情報。誰でも読める

② ペイロード

利用者IDや有効期限などの主張。誰でも読める=秘密を入れない

③ 署名

鍵で生成。改ざんされていないことを保証

JWTは3部構成。ヘッダとペイロードは“読めるだけ”、本物保証は署名が担う。

中身が「読めるだけ」なのが肝心です。だからペイロードはパスワードや個人情報を入れる場所ではありません。JWTの価値は、サーバが署名を検証することで「このトークンは自分が発行し、改ざんされていない」と確認できる点にあります。

安全に使うための防御

1

署名を必ず検証し、algを固定する(最重要)

サーバ側で署名を毎回検証する。受け入れる署名アルゴリズム(alg)を期待する値に固定し、alg:none(署名なし)は拒否する。トークンのヘッダに書かれたalgを鵜呑みにしない。

JWTのベストプラクティス(RFC 8725)は、ライブラリは呼び出し側が許可アルゴリズムの集合を指定できるようにしなければならず、それ以外は使ってはならないとしています。またJWT中のすべての暗号処理を検証し、1つでも失敗したらJWT全体を拒否しなければならないと定めています。

2

「alg:none」だけでなく、アルゴリズムの書き換えも防ぐ

alg の悪用は署名を消すことだけではありません。RFC 8725 は、RS256 を HS256 に書き換えられると、一部のライブラリが「RSA公開鍵をHMACの共有鍵として」署名を検証しようとすると説明しています(アルゴリズム混同)。公開鍵は誰でも入手できるので、これが通ると攻撃者が自分で有効な署名を作れてしまいます。

防ぎ方は同じです。ヘッダの alg を信用せず、サーバ側が期待するアルゴリズムを固定する。鍵の種類ごとに検証関数を選ぶのではなく、アルゴリズムを先に決めてから検証するのが正しい順番です。

3

「誰宛て・いつまで」まで検証する

署名が正しいことは「本物である」ことしか示しません。それが自分宛てで、まだ有効かは別の検証です。

RFC 7519 は aud(宛先)について「受け取った側が aud の値に自分を認めないなら、そのJWTは拒否しなければならない」、exp について「その時刻以降は処理のために受け入れてはならない」と定めています(nbf も同様に「その時刻より前は受け入れてはならない」)。RFC 8725 はさらに、発行者(iss)と鍵の対応を検証することを求めています。別サービス向けに正しく発行されたトークンが自分のところで通ってしまう事故は、この検証を飛ばしたときに起きます。

4

ペイロードに秘密を入れない

中身は誰でもデコードできる前提。パスワード・個人情報・APIキーなどの秘密は入れない。入れるのは識別子や権限など、漏れても致命的でない主張だけにする。

5

有効期限を短くし、失効の手段を持つ

アクセス用トークンは短命にする。長すぎる有効期限は、盗まれたときの被害時間を延ばす。失効が必要な設計なら、短命アクセストークン+サーバ側で管理するリフレッシュ/セッションを併用する。

6

強い鍵を使い、盗まれない場所に置く

署名鍵は十分に強く、使い回さず、サーバ側の安全な場所に保管する。トークン自体も、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デコーダに貼り、次の点を見ます。デコードはブラウザ内で行われ、トークンはサーバーに送られません。それでも本番の有効なトークンは、確認後にログアウトして無効にしておくと安心です。

  1. ヘッダの alg が想定どおりか(none になっていないか)
  2. exp(有効期限)があり、発行時刻(iat)からの長さが用途に合っているか
  3. aud・iss が入っていて、自分のサービスを指しているか
  4. ペイロードにメールアドレスや電話番号、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 に置かない)

次に読む

よくある質問

QJWTの中身は暗号化されていますか?
A

いいえ。標準的なJWTのヘッダとペイロードは暗号化ではなくbase64urlで“エンコード”されているだけで、トークンを持っている人は誰でも中身を読めます。だからパスワードや個人情報などの秘密をペイロードに入れてはいけません。JWTが守るのは『中身の秘匿』ではなく『改ざんされていないこと(署名による完全性)』です。

QJWTで一番危険な設定は何ですか?
A

署名を検証しない、または『alg:none(署名なし)』を受け入れてしまう実装です。これを許すと、攻撃者が中身を自由に書き換えた偽トークンを通せてしまいます。防御は、サーバ側で署名を必ず検証し、受け入れる署名アルゴリズム(alg)を期待する値に固定することです。

Qデコードと検証は同じですか?
A

違います。デコードは中身を読むだけで、誰でもできます(JWTデコーダで確認できます)。検証は、署名が正しいか・有効期限内か・発行者や対象が正しいかをサーバが秘密鍵/公開鍵で確かめる処理です。『デコードできた=正しいトークン』ではありません。本物かどうかを決めるのは必ずサーバ側の検証です。

QJWTとセッション(Cookie)はどちらを使うべき?
A

同じドメインの普通のWebアプリなら、サーバー側のセッションとCookieで十分なことが多く、ログアウトやアカウント停止もすぐ効きます。JWTが向くのは、複数のサービスやAPIが同じトークンを検証する場面や、外部の認証基盤(OAuth/OpenID Connect)からトークンを受け取る場面です。JWTを使う場合も、有効期限を短くし、サーバー側で取り消せる仕組みと組み合わせます。

QJWTはlocalStorageに保存してもいい?
A

おすすめしません。localStorageはJavaScriptから常に読めるため、XSSが1つあればトークンを盗まれます。OWASPも、セッションの識別子をlocalStorageに置かないよう勧めています。ブラウザで使うなら、HttpOnly・Secure・SameSiteを付けたCookieで運ぶ方が安全です。Cookieで運ぶ場合はCSRF対策も合わせて行います。

QJWTの「alg:none」とは?
A

ヘッダの alg(署名アルゴリズム)を none にした、署名のないJWTのことです。仕様上は存在しますが、ログインなどの認証に使うトークンで受け入れてはいけません。受け入れると、誰でも中身を書き換えたトークンを作れてしまいます。サーバー側で、受け入れるアルゴリズムを期待する値(例:RS256だけ)に固定して防ぎます。