コンテンツへスキップ

JWT/セッショントークンの基礎 — ステートフルとステートレス認証の違い

前々回の記事ではCookie+nonce認証、前回の記事ではWordPressの認証Cookieが「署名検証は自己完結・失効管理だけサーバー側のトークン一覧に頼る」ハイブリッド構成になっていることを見た。今回はその対極にある設計として、JWT(JSON Web Token)を取り上げる。JWTは「サーバー側にトークン一覧を一切持たない、完全にステートレスな認証」を目指した仕組みであり、WordPressのハイブリッド構成と比較することで、ステートフル認証とステートレス認証のトレードオフがより明確に見えてくる。

JWTとは何か — ドットで区切られた3つのパート

JWTは見た目上、xxxxx.yyyyy.zzzzz のようにドットで3分割された1本の文字列である。それぞれ「ヘッダー」「ペイロード」「署名」に対応する。ヘッダーには署名アルゴリズム(HS256 など)とトークンの種類、ペイロードにはユーザーIDや有効期限、権限などの「クレーム(claim)」と呼ばれる情報が入る。この2つをそれぞれBase64urlエンコードしてドットでつなぎ、その文字列全体に対してヘッダーで指定したアルゴリズムで署名した値が3つ目の部分になる。

補足: Base64urlは、URLやCookieの中に安全に入れられるよう + / = などの記号を - _ に置き換えた、通常のBase64のバリエーション。暗号化ではなく単なる文字コード変換であり、誰でも即座に元の文字列に戻せる。

「暗号化」ではなく「署名」であるという誤解しやすい点

ここでよく誤解されるのが、JWTのペイロード部分は「暗号化されているから中身は見えない」という思い込みだ。実際にはBase64urlエンコードされているだけで、暗号化は一切されていない。ブラウザの開発者ツールやコマンドラインで誰でも一瞬でデコードでき、ユーザーIDや権限といったクレームをそのまま読める。JWTが保証しているのは「内容の秘匿」ではなく「内容が発行後に改ざんされていないこと」だけであり、この点はT36で扱ったWordPressの認証CookieがHMAC署名で担保していた性質と全く同じである。裏を返せば、パスワードやAPIキーのような秘密情報をJWTのペイロードに入れてはいけない、という実務上の注意点にそのままつながる。

検証にデータベースを一切必要としない — 真のステートレス

T36で見たWordPressの認証Cookieは、HMAC署名の検証に加えて「このトークンが現在有効なトークン一覧に含まれているか」をユーザーメタ(データベース)に問い合わせる必要があった。この一覧照会があるからこそ「全ログアウト」が実現できていたわけだが、逆に言えば毎リクエストごとにデータベースへの問い合わせが発生しているということでもある。

JWTはこの照会そのものを省略する設計を取る。検証する側は、共有しているシークレットキー(HMACの場合)または公開鍵(RSA/ECDSAなど非対称鍵署名の場合)さえ持っていれば、トークン単体の署名を再計算して一致を確認するだけで検証が完了する。データベースへの問い合わせも、発行元サーバーへの問い合わせも一切不要だ。これが「ステートレス」と呼ばれる理由であり、複数のマイクロサービスが同じ認証基盤を共有しなければならない分散システムでJWTが好まれる最大の理由でもある。各サービスは中央のセッションストアに毎回問い合わせることなく、手元の鍵だけで独立に検証を完結できる。

代償として失われるもの — 個別トークンの即時失効ができない

このステートレスな設計には明確な代償がある。一度発行されたJWTは、有効期限が来るまで、署名さえ正しければどのサーバーでもいつでも検証に通ってしまう。T36のWordPressの「全ログアウト」のように、特定のトークンだけをサーバー側から強制的に無効化する手段が、素のJWTには存在しない。ユーザーメタのようなトークン一覧を持たないというまさにその設計判断が、ステートレスさと引き換えに個別失効の手段を手放しているのである。

現実的な妥協策 — 完全なステートレスは実は貫きにくい

この弱点への対策として、実務では大きく2つのアプローチが取られる。1つ目は有効期限を数分から数十分程度に短く設定し、その代わりに「リフレッシュトークン」という別の長寿命トークンをサーバー側で管理して定期的に新しいJWTを再発行する方式だ。この場合、リフレッシュトークンの一覧管理という形で結局サーバー側の状態を一部だけ持つことになるが、短寿命のJWT自体は依然としてステートレスに検証できる。2つ目は、失効させたいトークンのID(jti クレーム)をブロックリストとしてサーバー側に保持し、検証のたびにこのリストと照合する方式だが、これは実質的にT36で見たWordPressのトークン一覧照会と同じ発想に戻っており、ステートレスの利点をかなりの部分放棄することになる。「完全にステートレスでありながら即時失効もできる」という両立は、原理的に難しいことがこの2つの妥協策からも見て取れる。

どちらを選ぶべきか — システム構成による使い分け

WordPressのようにデータベースが既に中心に1つ存在し、すべてのリクエストがそこに到達できる構成では、T36で見たハイブリッド方式(署名検証は自己完結・失効管理だけデータベース)が理にかなっている。データベースへの問い合わせコストは元から発生しており、そこにトークン一覧の照会を1つ追加する程度の負担で、即時失効という実用上重要な機能を得られるからだ。

一方で、複数の独立したサービスが横に並ぶ分散システムや、モバイルアプリが複数の異なるバックエンドAPIを呼び分けるような構成では、リクエストのたびに中央のセッションストアへ問い合わせること自体が構成上のボトルネックやネットワーク依存になりやすい。この場合はJWTのように「鍵さえあれば手元で独立に検証できる」性質が強みになる。個別トークンの即時失効という機能を多少犠牲にしてでも、短い有効期限とリフレッシュトークンの組み合わせで運用する方が、システム全体としては扱いやすくなる場面が多い。

まとめ

JWTは、ヘッダー・ペイロード・署名の3パートで構成され、ペイロードは暗号化ではなく単なるエンコードにすぎない。最大の特徴は、サーバー側にトークン一覧を持たずに署名だけで検証が完結する真のステートレス性にあり、これは分散システムでの検証コストを大きく下げる。しかしその代償として、個別トークンの即時失効ができないという制約を抱えており、短い有効期限とリフレッシュトークンの組み合わせやブロックリストといった妥協策で運用されるのが実情だ。前回のCookie/セッション認証の記事で見たWordPressのハイブリッド構成は、この「ステートレスな検証速度」と「ステートフルな失効管理」という2つの要求を、1つの認証基盤の中でどちらも部分的に満たそうとした設計だったと捉えると、両者の違いがより立体的に見えてくる。