APIリクエストに Authorization: Bearer eyJhbGci... ヘッダーを付けていたら 「Invalid token」エラーが返ってきた——そんな経験はないでしょうか。 JWTは現代のWeb開発で欠かせない認証トークン形式ですが、「デコードできる=暗号化されていない」という 意外な事実や、ローカルストレージへの保存が危険な理由など、知っておかないと痛い目を見る落とし穴が いくつかあります。この記事では、JWTの仕組みを構造から順に解説します。
JWTとは
JWT(JSON Web Token、ジョット)は、JSONデータをコンパクトに表現してデジタル署名を付与するための オープン標準(RFC 7519)です。Web APIの認証・認可、マイクロサービス間の信頼確立、シングルサインオン (SSO)など、主にユーザーのログイン状態を安全にやり取りする場面で広く使われています。
従来のセッションベース認証では、サーバーがメモリやDBでセッション情報を管理していました。 JWTではユーザー情報と有効期限をトークン自体に埋め込んで署名するため、 サーバーがセッション情報を保持する必要がなく、複数サーバーへのスケールアウトが容易です。
JWTの3つの構成要素
JWTは3つのパーツをピリオド(.)で区切った文字列です。
# JWTの構造
ヘッダー.ペイロード.署名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTc0NTI0MDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
各パーツはBase64URLエンコードされています。Base64URLはURLセーフな文字のみを使う変換方式で、 暗号化ではありません。したがってデコードすれば誰でも中身を読むことができます。
ヘッダー(Header)
使用するアルゴリズム(alg)とトークンの種類(typ)を宣言します。
// ヘッダーのデコード例
{
"alg": "HS256",
"typ": "JWT"
}
ペイロード(Payload)
ユーザー情報や有効期限などの「クレーム(claim)」を格納するJSONオブジェクトです。
// ペイロードのデコード例
{
"sub": "user_123",
"name": "田中太郎",
"role": "admin",
"iat": 1745236400,
"exp": 1745240000
}
署名(Signature)
ヘッダーとペイロードをつなげた文字列を、秘密鍵でハッシュ化した値です。 受信者は同じ計算を行い、署名が一致すれば「改ざんされていない」と確認できます。 逆に言えば、署名の検証なしにペイロードを信頼してはいけません。
ペイロードに含まれる標準クレーム
RFC 7519では以下の「登録済みクレーム(Registered Claims)」が標準化されています。
| クレーム名 | 正式名 | 説明 | 例 |
|---|---|---|---|
| iss | Issuer | トークンの発行者 | "example.com" |
| sub | Subject | トークンの主体(ユーザーID等) | "user_123" |
| exp | Expiration Time | 有効期限(UNIXタイムスタンプ) | 1745240000 |
| iat | Issued At | 発行日時(UNIXタイムスタンプ) | 1745236400 |
| jti | JWT ID | トークンの一意識別子(リプレイ攻撃防止) | "abc-123-xyz" |
これらに加えて、アプリケーション固有の情報(ユーザー名・権限ロールなど)を プライベートクレームとして自由に追加できます。ただし機密情報の埋め込みは避けましょう。
署名の仕組み — HS256とRS256の違い
署名に使うアルゴリズムは主に2種類あります。
| アルゴリズム | 方式 | 署名 | 検証 | 向いている構成 |
|---|---|---|---|---|
| HS256 | 共通鍵(HMAC-SHA256) | 共有秘密鍵 | 同じ共有秘密鍵 | 単一サービス・シンプルな構成 |
| RS256 | 公開鍵暗号(RSA-SHA256) | 秘密鍵(発行者のみ) | 公開鍵(誰でも取得可) | マイクロサービス・IDaaS連携 |
HS256では署名も検証も同じ「共有秘密鍵」を使います。実装がシンプルで高速ですが、 複数サービスに秘密鍵を共有すると、1か所が侵害されただけで全サービスが危険にさらされます。
RS256では秘密鍵でしか署名できないが、公開鍵があれば誰でも検証できます。発行サービス(認証サーバー)は秘密鍵を厳重に管理し、各マイクロサービスには公開鍵だけを配布すれば 検証が可能です。AWSのCognito・Google・Auth0などIDaaSの多くがRS256を採用しています。
デコードと検証の違い
ここが最も重要な概念です。
デコードとは、Base64URLエンコードされたヘッダー・ペイロードを元のJSONに戻す操作です。 これは誰でも・鍵なしで・即座にできます。JWTデコードツールでJWTを貼り付けるだけでペイロードの中身が確認できます。
検証(Verification)とは、署名が正しいかを確認する操作です。 HS256であれば共有秘密鍵、RS256であれば公開鍵が必要です。 検証なしにペイロードを信頼することは、「封筒を開けた字が本物かどうか確かめずに署名する」のと同じです。
重要
JWTはデコードできますが、それだけでは「改ざんされていない」とは証明できません。 サーバー側では必ず署名の検証を行ってからペイロードの内容を使用してください。
セキュリティ上の注意点
ローカルストレージへの保存は危険
JWTをJavaScriptの localStorage やsessionStorage に保存すると、 XSS(クロスサイトスクリプティング)攻撃でサードパーティスクリプトが実行された際にトークンが盗まれます。 盗まれたトークンを使って攻撃者がAPIにアクセスし放題になります。
推奨は HttpOnly属性付きのSecure Cookie への保存です。HttpOnly 属性があるCookieはJavaScriptから読み取れないため、 XSS経由での盗取を防げます。あわせてCSRF対策として SameSite=Strictまたは SameSite=Lax の設定も必要です。
有効期限を短く設定する
JWTはサーバー側に状態を持たないため、一度発行したトークンを即時無効化することが難しいです。 ユーザーがログアウトしても、JWTの有効期限が残っていれば技術的には使い続けられます。
対策は2つです。①expを短くする(15〜60分程度)、 ②リフレッシュトークンと組み合わせる(アクセストークンは短命、リフレッシュトークンで再発行)。 重要操作(決済・パスワード変更など)では再認証を要求する仕組みも有効です。
ペイロードに機密情報を含めない
前述の通り、ペイロードは誰でもデコードできます。 パスワード・クレジットカード情報・詳細な個人情報(住所・電話番号など)はペイロードに含めてはいけません。 ユーザーIDや権限ロールなど、サーバーが処理に使う最小限の情報のみを格納するのが原則です。
まとめ
- JWTはヘッダー・ペイロード・署名の3パーツで構成され、ピリオドで区切られる
- ペイロードはBase64URLエンコードされているだけで、誰でもデコードして読める
- デコードと検証は別物。サーバーでは必ず署名を検証してからペイロードを信頼する
- HS256は共有秘密鍵、RS256は公開鍵方式。複数サービスではRS256が安全
- JWTはlocalStorageに保存せず、HttpOnly Cookieに保存する
- 有効期限(exp)は短く設定し、リフレッシュトークンと組み合わせる
- 機密情報はペイロードに含めない