セキュリティ · 5分で読めます ·

JWTの中身と、「デコード」が「検証」ではない理由

JSON Web Tokenはランダムな文字列に見えますが、誰でも中身を読めます。3つの部分に何が入っているのか、そしてセキュリティホールにつながるミスを解説します。

JWT(JSON Web Token)は、多くのWebアプリやAPIでログイン状態を保つための長い文字列です。暗号化されているように見えますが、ほとんどの場合そうではありません。トークンを持っている人なら誰でも中身を読めます。

ドットで区切られた3つの部分

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiJ9.3q2-7w…
└ header ──────────┘ └ payload ───────┘ └ signature

headerには、HS256やRS256など、トークンの署名方法が書かれています。payloadにはクレーム、つまりユーザーが誰か(sub)、いつ発行されたか(iat)、いつ期限が切れるか(exp)と、ロールなどアプリが加えた情報が入っています。どちらもbase64urlでエンコードしただけのJSONで、これは暗号化ではなくエンコードです。

大切なのは署名

3つ目の部分は、最初の2つに対する署名で、秘密鍵や共有シークレットで作られます。正しい鍵を持つサーバーだけが検証できます。payloadを1文字でも書き換えれば、たとえばロールをuserからadminに変えれば、署名は一致しなくなります。

デコードは検証ではない

問題なくデコードできるトークンが教えてくれるのは「何を主張しているか」で、「それが本当か」ではありません。バックエンドでは必ず鍵で署名を検証し、expを過ぎたトークンを拒否し、想定したアルゴリズムだけを受け入れてください。「none」は決して許してはいけません。JWTライブラリがこれを行ってくれます。省略するのは典型的な脆弱性です。

実践的なルール

payloadは読めるので、パスワードなどの秘密を入れないこと。有効期限は短くすること。有効な間はトークンをパスワードと同じように扱うこと。コピーされれば、期限が切れるまで誰でも使えてしまいます。

ブラウザのどこにトークンを保存するか

localStorageに保存したものは、ページ上のどのスクリプトからでも読めます。クロスサイトスクリプティング(XSS)のバグがひとつあるだけで、トークンが漏れるおそれがあります。HttpOnlyのCookieはJavaScriptから読めないので、このリスクはなくなります。SecureとSameSiteの属性、そしてクロスサイトリクエストフォージェリ(CSRF)対策と組み合わせましょう。完璧な方法はありませんが、セッショントークンにはHttpOnlyのCookieがより安全な選択です。

有効期限とリフレッシュトークン

JWTはそれ自体を「ログアウト」させることができません。サーバーが失効リストを持っていない限り、expの時刻まで有効なままです。そのためアクセストークンの寿命は通常、日単位ではなく分単位です。より長く有効なリフレッシュトークンを慎重に保管してサーバーで検証し、古いアクセストークンが期限切れになったら新しいものを取得します。

よく使うクレーム一覧

iss  発行者      トークンを作成したのは誰か
sub  主体        対象となるユーザーやクライアント
aud  対象者      どのサービスが受け付けるべきか
exp  有効期限    これを過ぎると無効になるUnix時刻
nbf  開始時刻    これより前は無効なUnix時刻
iat  発行時刻    作成されたUnix時刻

時刻のクレームは秒単位のUnixタイムスタンプです。サーバーはexpだけでなくaudも確認する必要があります。あるサービス向けに発行された有効なトークンを、別のサービスが受け付けてはいけません。

トークンを読む

4DevのJWTデコーダーは、トークンを各部分に分け、headerとpayloadを読みやすいJSONで表示します。デコードするだけで、署名は確認しません。トークンはデコードのために4Devのサーバーに送られますが、記録も保存もされません。それでも本番環境の有効なトークンは貼り付けず、漏れた可能性のあるトークンは失効させてください。

JWTデコーダーを開く

LinkedInXFacebook