JWT
署名付きでユーザー情報を運ぶトークン。サーバに状態を持たない認証を可能にする。
概要
JWT(JSON Web Token、「ジョット」と読まれることが多い)は、JSON 形式のデータに署名を付けて一つの文字列にまとめたトークンです。中身には「ユーザーID は 42、権限は admin、有効期限は1時間後」といった情報(クレームと呼びます)が入っており、受け取った側は署名を検証するだけで「発行者が確かに発行したもので、途中で改ざんされていない」ことを自力で確認できます。
最大の特徴は、検証のために発行元へ問い合わせたりデータベースを引いたりする必要がないことです。セッション方式が「サーバ側に状態を保存し、クライアントには番号札だけ渡す」のに対し、JWT は「情報そのものを署名付きでクライアントに持たせる」方式だと言えます。ログイン後の認証状態の維持や、OAuth のアクセストークン・OpenID Connect の ID トークンの実装形式として広く使われています。
なぜ生まれたか
伝統的なセッション方式では、ログイン状態はサーバのメモリやデータベースに保存されます。サーバが1台なら単純ですが、複数台に分散すると「どのサーバでもセッションを参照できる」ようにセッションストアを共有する必要があり、API を提供するサービスが増えるほど、全サービスがその共有ストアに依存する構図になります。さらに、組織をまたいで認証情報を受け渡すソーシャルログインのような場面では、そもそも共有ストアという発想が成り立ちません。
そこで「検証に必要な情報をトークン自体に埋め込み、署名で真正性を保証する」という自己完結型のトークンが求められ、2015年に RFC 7519 として JWT が標準化されました。受け取る側は署名鍵(または公開鍵)さえ持っていれば、発行元に問い合わせることなく単独で検証できます。この性質が、サーバを水平に増やすスケーリングや、サービス間で認証情報を伝搬させるマイクロサービスアーキテクチャと噛み合ったことで、急速に普及しました。
詳細
3つのパートからなる構造
JWT は xxxxx.yyyyy.zzzzz のように、ドットで区切られた3つのパートで構成されます。1つ目はヘッダで、署名アルゴリズムの種類などを記した JSON。2つ目はペイロードで、ユーザーID(sub)、発行者(iss)、有効期限(exp)などのクレームを収めた JSON。3つ目が、前の2つに対する署名です。ヘッダとペイロードはそれぞれ Base64URL という方式で文字列化されて連結されます。
ここで重要なのは、Base64URL は暗号化ではなくただのエンコードだという点です。JWT のペイロードは誰でもデコードして読めるので、パスワードや個人情報のような秘密を入れてはいけません。署名が保証するのは「改ざんされていないこと」であって「見えないこと」ではない — これは JWT の最頻出の誤解です。
署名と検証の仕組み
署名には大きく2系統あります。HMAC(HS256 など)は共有秘密鍵とハッシュ化を組み合わせる方式で、発行者と検証者が同じ鍵を持つ前提です。RSA や ECDSA(RS256、ES256 など)は公開鍵暗号を使う方式で、発行者だけが秘密鍵で署名し、検証者は公開鍵で検証します。認可サーバが発行して多数のサービスが検証する構成では、鍵を配り歩かなくて済む公開鍵方式が標準的です。
発行から検証までの流れを図にすると、「API サーバが認証サーバに問い合わせない」という JWT の要点がよく見えます。
検証する側は、署名の正当性に加えて、exp(有効期限切れでないか)、iss(想定した発行者か)、aud(自分宛てのトークンか)を必ず確認します。ライブラリ任せにしつつも、どのクレームを検証しているかは把握しておくべきポイントです。
「誰でも読めるが、改ざんすれば検証で弾かれる」という性質は、実際にペイロードを書き換えてみると一目で分かります。
(JSON)
{"sub":"user123","role":"user"}(3部構成)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6InVzZXIifQ.署名中…ペイロードは Base64URL のただのエンコードなので誰でも書き換えられますが、書き換えた瞬間に署名と食い違い、検証で弾かれます。 正しい署名を作り直せるのは秘密鍵を持つサーバだけ — 鍵を知らない攻撃者には「✔ 有効」に戻す手段がありません。
最大の弱点: 失効させにくい
自己完結であることの裏返しとして、JWT は「発行後に無効化しにくい」という構造的な弱点を持ちます。セッションならサーバ側のレコードを消せば即座にログアウトさせられますが、JWT は有効期限が来るまで、署名としては正しいまま生き続けます。「ユーザーを強制ログアウトさせたい」「漏えいしたトークンを止めたい」という要求に、そのままでは応えられません。
実務ではこれを、アクセストークンを短命(数分〜1時間)にし、長命のリフレッシュトークンで再発行する二段構えで緩和します。リフレッシュトークンの方はサーバ側で管理して失効可能にしておけば、被害の窓を短く抑えられます。それでも足りない場合は失効リストをサーバで持つことになりますが、そうすると「状態を持たない」利点が薄れるため、そもそもセッション方式でよかったのでは、という設計判断に立ち返ることになります。
落とし穴と使いどころの判断
歴史的に有名な脆弱性として、ヘッダの alg クレームを攻撃者が none(署名なし)に書き換えても通ってしまう実装や、公開鍵方式のトークンを HMAC として解釈させて公開鍵を「共有鍵」として署名を偽造できてしまう混同攻撃がありました。現代のライブラリでは対策済みですが、「許可するアルゴリズムを検証側で固定する」のが鉄則です。また、ブラウザに保存する場合、localStorage は XSS で盗まれるリスクがあるため、HttpOnly な Cookie に載せる構成が推奨されることが多い、という保存場所の議論も定番です。
使い分けの目安としては、単一アプリで即時失効が重要ならセッション、複数サービス・組織間で認証情報を運ぶなら JWT、という整理が出発点になります。「モダンだから JWT」ではなく、状態管理の所在と失効要件から選ぶのが本質です。