「ログイン状態ってどうやって保持してるの?」「APIのアクセス権限ってどう管理するのが安全?」
Webサービスやアプリを開発していると、こんな疑問にぶつかることはありませんか? その答えの一つが、今回ご紹介する**JWT(JSON Web Token)**です。
なんだか難しそうなアルファベット3文字ですが、現代のWeb開発、特にAPI連携や認証・認可において非常に重要な技術なんです。でも、その仕組みやセキュリティについて、ちゃんと理解できている人は意外と少ないかもしれません。
この記事では、JWTの専門家チームが、まったくの初心者の方から、すでにある程度知識のある方まで、「JWTとは何か?」を世界一わかりやすく、そして深く解説します!
- JWTって、そもそも何のためにあるの?
- あの長い文字列(トークン)の中身はどうなってるの?
- 認証や認可で、具体的にどうやって使うの?
- セキュリティは大丈夫? 注意点は?
こんな疑問に、図解や具体例をたっぷり使って、丁寧にお答えしていきます。この記事を読めば、あなたもJWTを自信を持って使いこなせるようになるはずです!
この記事の概要:
- JWTの基本的な概念と、なぜ必要なのか
- JWTトークンの構造(ヘッダー・ペイロード・署名)の詳細解説
- JWTの主な利用シーン(認証・認可)
- 従来のセッション管理との違い、JWTのメリット・デメリット
- 絶対に知っておくべきJWTのセキュリティ上の注意点と対策
- 簡単な実装例とライブラリ紹介
さあ、JWTの世界へ一緒に飛び込んでいきましょう!
JWT(JSON Web Token)とは何か? なぜ必要なのか?
まず、JWTが何の略で、どんなものなのかを見ていきましょう。
JWTは「JSON Web Token」の略で、「ジョット」と読むことが多いです。これは、**JSON形式で情報を表現し、その情報が改ざんされていないことをデジタル署名によって保証できる「トークン(Token)」**に関するオープンスタンダード(RFC 7519)です。
簡単に言うと、**「安全に情報をやり取りするための、自己完結型のデータ形式」**のことです。
なぜJWTが必要なのでしょうか?
従来のWebアプリケーションでは、「セッション管理」という方法でユーザーのログイン状態などを管理するのが一般的でした。これは、サーバー側でユーザーごとの情報を保存しておく方式です。
しかし、以下のようなケースでは、従来のセッション管理が不得意な場面が出てきました。
- ステートレスなAPI: サーバー側で状態(State)を持たないAPI。特にマイクロサービスアーキテクチャでは、各サービスが独立しているため、共通のセッションストアを持つのが難しい。
- 複数のドメイン/サービス間での認証連携: いわゆるシングルサインオン(SSO)のような仕組み。
- モバイルアプリとの連携: WebブラウザのCookieに頼れない環境。
こうした課題を解決するのがJWTです。JWTは、ユーザー情報や権限情報などをトークン自体に含み、かつ署名によって検証可能なので、サーバー側で状態を保持する必要がありません(ステートレス)。これにより、スケーラビリティが高く、異なるサービス間での連携も容易になります。
主なメリット:
- ステートレス: サーバー側でセッション情報を保持する必要がなく、サーバー負荷軽減やスケーラビリティ向上に繋がる。
- サービス間の連携が容易: トークン自体に必要な情報が含まれているため、マイクロサービスや外部APIとの連携に適している。
- 多様な環境での利用: Cookieが使えないモバイルアプリやIoTデバイスなどでも利用しやすい。
主なデメリット:
- トークンのサイズ: 多くの情報を含めるとトークンが長くなり、リクエストヘッダーのサイズが増加する可能性がある。
- 一度発行したトークンの失効が難しい: 有効期限が切れるまで、基本的にトークンは有効。強制的に失効させるには追加の仕組み(ブラックリストなど)が必要。
- ペイロードの情報漏洩リスク: ペイロードは署名されているだけで暗号化はされていない(JWSの場合)。機密情報は含めるべきではない。
JWTの構造:ヘッダー・ペイロード・署名の詳細解説
JWTトークンは、一見するとランダムな長い文字列に見えますが、実は明確な構造を持っています。それは、ピリオド(.
)で区切られた3つの部分から構成されています。
xxxxx.yyyyy.zzzzz
- ヘッダー (Header)
- ペイロード (Payload)
- 署名 (Signature)
これらはそれぞれ、Base64URLエンコードという方式でエンコードされています。(Base64をURLセーフにしたもの)
では、各部分を詳しく見ていきましょう。
1. ヘッダー (Header)
ヘッダーには、トークンのタイプ(通常は”JWT”)と、使用されている署名アルゴリズム(例: HS256, RS256)などのメタ情報がJSON形式で格納されています。
JSON
{
"alg": "HS256",
"typ": "JWT"
}
alg
(Algorithm): 署名に使用するアルゴリズムを指定します。HS256
(HMAC-SHA256)やRS256
(RSA-SHA256)などがよく使われます。none
は絶対に指定してはいけません!(理由は後述)typ
(Type): トークンのタイプを指定します。通常はJWT
です。
このJSONオブジェクトがBase64URLエンコードされて、トークンの最初の部分になります。
2. ペイロード (Payload)
ペイロードには、**クレーム(Claims)**と呼ばれる、実際の情報がJSON形式で格納されています。クレームは、ユーザーID、名前、権限、トークンの有効期限など、伝えたい情報を表すキーと値のペアです。
クレームには、主に3つの種類があります。
- 予約済みクレーム (Registered Claims): JWTの仕様であらかじめ定義されているクレーム。これらは必須ではありませんが、使うことが推奨されています。
iss
(Issuer): トークンの発行者sub
(Subject): トークンの主題(通常はユーザーID)aud
(Audience): トークンの受信者(対象となるサービスやAPI)exp
(Expiration Time): トークンの有効期限(Unixタイムスタンプ)←非常に重要!nbf
(Not Before): トークンが有効になる日時(Unixタイムスタンプ)iat
(Issued At): トークンが発行された日時(Unixタイムスタンプ)jti
(JWT ID): トークンの一意な識別子(リプレイアタック対策などに利用)
- 公開クレーム (Public Claims): JWTを使用する当事者間で自由に定義できるクレーム。ただし、名前の衝突を避けるために、IANA JSON Web Token Claims Registryに登録するか、衝突しにくいURI形式の名前空間を使用することが推奨されます。
- プライベートクレーム (Private Claims): トークンの発行者と受信者の間で合意された、自由に定義できるクレーム。名前の衝突に注意が必要です。
JSON
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iss": "https://example.com",
"exp": 1711584000 // 例えば、2025年3月28日 00:00:00 UTC
}
【重要】ペイロードは暗号化されていない!
ヘッダーと同様に、このJSONオブジェクトもBase64URLエンコードされてトークンの2番目の部分になります。重要なのは、Base64URLエンコードは単なる符号化であり、暗号化ではないということです。つまり、誰でも簡単にデコードして中身を読むことができます。
したがって、ペイロードにはパスワードやクレジットカード番号などの機密情報を含めてはいけません。
もし機密情報を送る必要がある場合は、JWTを暗号化する**JWE (JSON Web Encryption)**という別の仕様を使う必要がありますが、一般的にはJWS (JSON Web Signature) が使われることが多いです。この記事では主にJWSについて解説しています。
3. 署名 (Signature)
署名は、トークンが改ざんされていないことを保証するための非常に重要な部分です。
以下の手順で作成されます。
- エンコードされたヘッダー (
xxxxx
) - ピリオド (
.
) - エンコードされたペイロード (
yyyyy
)
これらを連結した文字列を、ヘッダーで指定されたアルゴリズム (alg
) と、秘密鍵 (Secret) を使って計算します。
署名アルゴリズムの種類:
- HMAC系 (例: HS256, HS384, HS512):
- 共通鍵暗号方式を使用します。
- トークンの生成側と検証側で同じ秘密鍵を共有します。
- 計算が比較的軽量です。
- サーバー内部など、鍵を安全に共有できる範囲での利用に適しています。
- RSA系 / ECDSA系 (例: RS256, ES256):
- 公開鍵暗号方式を使用します。
- トークンの生成側は秘密鍵で署名し、検証側は公開鍵で検証します。
- 鍵の管理が柔軟になります(公開鍵は公開しても安全)。
- 異なるサービス間での連携や、クライアント側での検証に適しています。
- 計算負荷はHMAC系より高くなります。
計算された署名データがBase64URLエンコードされて、トークンの最後の部分 (zzzzz
) になります。
署名の検証プロセス:
トークンを受け取った側(サーバーなど)は、以下の手順で署名を検証します。
- 受け取ったJWTのヘッダーとペイロード部分を取り出す。
- ヘッダーで指定されたアルゴリズムと、**サーバーが保持している秘密鍵(HS系の場合)または公開鍵(RS/ES系の場合)**を使って、署名を再計算する。
- 再計算した署名と、受け取ったJWTの署名部分が一致するかどうかを確認する。
もし署名が一致すれば、ヘッダーとペイロードが途中で改ざんされていないことが保証されます。一致しなければ、トークンは不正なものとして拒否されます。
【超重要】alg: "none"
の危険性
ヘッダーのalg
にnone
を指定すると、署名がないトークンを作成できます。これは絶対に避けなければなりません。悪意のあるユーザーが、alg
をnone
に書き換え、ペイロードを好きな内容(例: admin: true
)に変更したトークンを送りつけてくる攻撃(CVE-2015-2951 など)が存在します。ライブラリによっては、alg: "none"
を許可しない設定になっていることが多いですが、実装時には必ず確認が必要です。
JWTの利用例:認証と認可
JWTは主に、**認証(Authentication)と認可(Authorization)**の文脈で使われます。
認証 (Authentication)
ユーザーが誰であるかを確認するプロセスです。JWTを使った一般的な認証フローは以下のようになります。
- ログイン: ユーザーがIDとパスワードなどでログインリクエストを送る。
- サーバーでの検証: サーバーはユーザー情報を検証する。
- JWT発行: 検証が成功したら、サーバーはユーザー情報(例: ユーザーID)や有効期限を含むJWTを生成し、署名してユーザー(クライアント)に返す。
- JWTの保存: クライアント(ブラウザやアプリ)は受け取ったJWTを安全な場所(例:
localStorage
,sessionStorage
, モバイルアプリ内のセキュアストレージ)に保存する。 - リクエスト送信: クライアントは、以降の保護されたリソースへのリクエスト時に、HTTPヘッダー(通常は
Authorization: Bearer <token>
)にJWTを含めて送信する。 - サーバーでの検証: サーバーは受け取ったJWTの署名を検証し、有効期限などをチェックする。検証が成功すれば、リクエストを処理し、結果を返す。
この方式により、サーバーはユーザーのリクエストごとにデータベースに問い合わせる必要がなくなり、ステートレスな認証が実現できます。
認可 (Authorization)
ユーザーが特定のリソースや操作に対してアクセス権を持っているかを確認するプロセスです。
JWTのペイロードにユーザーの**ロール(役割)やパーミッション(権限)**情報を含めることで、認可にも利用できます。
JSON
{
"sub": "user123",
"exp": 1711670400,
"roles": ["editor", "viewer"],
"permissions": ["article:create", "article:read"]
}
サーバーはJWTを検証する際に、ペイロード内のロールやパーミッション情報を読み取り、要求された操作が許可されているかどうかを判断します。
注意点: ロールやパーミッション情報は、変更される可能性があります。JWTに含めると、トークンの有効期限が切れるまで古い情報が使われ続けるリスクがあります。頻繁に変更される情報はJWTに含めず、必要に応じてサーバー側で最新情報を参照するなどの工夫が必要です。
JWT vs 従来のセッション管理
ここで、JWTと従来のCookieベースのセッション管理を比較してみましょう。
特徴 | JWT (ステートレス) | 従来のセッション管理 (ステートフル) |
---|---|---|
状態管理 | サーバー側で状態を持たない (Stateless) | サーバー側でセッション情報を保持 (Stateful) |
データ格納場所 | トークン自体 (クライアント側) | サーバー側のメモリ、DB、キャッシュなど |
スケーラビリティ | 高い (サーバー間で状態共有不要) | 低い (状態共有の仕組みが必要) |
CORS | ヘッダーで送受信するため、比較的容易に対応可能 | Cookieのドメイン制約があり、設定が複雑になる場合がある |
モバイル/他環境 | Cookieに依存しないため、利用しやすい | Cookieが使えない環境では扱いにくい |
トークンサイズ | 情報量に応じて増加 | 通常はセッションIDのみで小さい (Cookie) |
強制失効 | 難しい (追加の仕組みが必要) | 容易 (サーバー側でセッション情報を削除) |
情報漏洩リスク | ペイロードはデコード可能 (機密情報はNG) | セッションIDが漏洩すると成りすましのリスク (サーバー側は安全) |
CSRF対策 | Cookieを使わない場合は基本的に不要 (ただし注意点あり※) | 必要 (トークン、SameSite属性など) |
※JWTをlocalStorage
などに保存する場合、XSS(クロスサイトスクリプティング)脆弱性があるとトークンが盗まれるリスクがあります。また、Cookie(特にHttpOnly属性なし)に保存する場合はCSRF対策が必要です。
どちらを使うべきか?
どちらが良いかは、アプリケーションの要件やアーキテクチャによります。
- JWTが適しているケース:
- ステートレスなAPI、マイクロサービスアーキテクチャ
- モバイルアプリやSPA(シングルページアプリケーション)の認証
- 複数のドメイン間での認証連携(SSOの一部など)
- 短期間で有効期限が切れる一時的な認証情報
- 従来のセッション管理が適しているケース:
- モノリシックなWebアプリケーション
- サーバー側でユーザーの状態を細かく管理したい場合
- トークンの強制失効が頻繁に必要な場合
- セキュリティ要件が非常に高く、クライアント側に情報を持ちたくない場合
絶対に知っておくべきJWTのセキュリティ上の注意点!
JWTは便利ですが、使い方を間違えると深刻なセキュリティリスクに繋がります。以下の点に必ず注意してください。
- HTTPSを常に使用する: HTTP通信ではトークンが平文でネットワーク上を流れるため、盗聴されるリスクがあります。必ずHTTPSで通信し、通信経路を暗号化してください。
- 秘密鍵/鍵ペアを厳重に管理する:
- HS系 (共通鍵): 秘密鍵が漏洩すると、誰でも有効なトークンを生成・改ざんできてしまいます。推測されにくい十分に複雑な鍵を使用し、安全な場所に保管してください。
- RS/ES系 (公開鍵): 秘密鍵は発行サーバーのみが厳重に管理します。公開鍵は検証に必要なサーバーに配布します。
- 強力な署名アルゴリズムを選択する:
alg: "none"
は絶対に許可しない。HS256
は最低限とし、可能であればRS256
やES256
、より強力なアルゴリズム(SHA-384/512ベース)の使用を検討してください。ライブラリがデフォルトで安全な設定になっているか確認しましょう。 - ペイロードに機密情報を含めない: パスワード、個人情報などの機密情報は絶対に含めないでください。必要な場合はJWEによる暗号化を検討するか、サーバー側で別途管理してください。
- 有効期限 (
exp
) を必ず設定し、短めにする: トークンが漏洩した場合の影響を最小限にするため、有効期限は必須です。アプリケーションの要件に合わせて、可能な限り短く設定しましょう(例: 15分〜1時間)。 - トークン漏洩対策 (XSS, CSRF):
- XSS対策:
localStorage
やsessionStorage
にJWTを保存すると、XSS脆弱性がある場合にトークンが盗まれる可能性があります。入力値のサニタイズなど、基本的なXSS対策を徹底してください。 - Cookie保存の場合:
HttpOnly
属性とSecure
属性を必ず設定し、CSRF対策(SameSite属性Lax
またはStrict
、CSRFトークンなど)を行ってください。
- XSS対策:
- トークンの失効(ブラックリスト/リフレッシュトークン): JWTは一度発行すると有効期限まで有効なのが基本です。ユーザーのパスワード変更時や強制ログアウト時にトークンを無効化したい場合は、追加の仕組みが必要です。
- ブラックリスト: 失効させたいトークンのID(
jti
クレーム)をサーバー側のリスト(DBやキャッシュ)に保存し、リクエストごとにチェックする方法。ステートレス性が損なわれる可能性があります。 - リフレッシュトークン: アクセストークン(JWT)の有効期限を非常に短く(例: 5-15分)設定し、有効期限の長い別の「リフレッシュトークン」を発行します。アクセストークンの有効期限が切れたら、リフレッシュトークンを使って新しいアクセストークンを再取得します。ユーザーを強制ログアウトさせたい場合は、リフレッシュトークンを無効化します。これが現在主流の方法の一つです。
- ブラックリスト: 失効させたいトークンのID(
- アルゴリズム混同攻撃 (Algorithm Confusion) に注意: ライブラリによっては、ヘッダーの
alg
をHS256
に変更し、公開鍵を秘密鍵として使うことで署名を偽造できる脆弱性がありました。使用するライブラリがこの脆弱性に対応しているか確認してください。 - リプレイアタック対策:
jti
クレーム(JWT ID)とexp
クレームを適切に使用し、一度使用されたトークンや期限切れのトークンを拒否するようにしてください。
セキュリティは「これで完璧」というものはありません。常に最新の脆弱性情報を収集し、ライブラリを最新の状態に保ち、適切な対策を講じ続けることが重要です。 専門家の意見(例:OWASP JWT Cheat Sheet ※リンクはJava用ですが概念は共通)なども参考にしてください。
簡単な実装例とライブラリ紹介
多くのプログラミング言語で、JWTを扱うためのライブラリが提供されています。ここでは、JavaScript (Node.js) と Python の例を簡単に紹介します。
JavaScript (Node.js) の例 (jsonwebtoken
ライブラリ)
Bash
npm install jsonwebtoken
JavaScript
const jwt = require('jsonwebtoken');
// 秘密鍵 (環境変数などから読み込むのが安全)
const secretKey = 'your-very-strong-secret-key'; // ★絶対にハードコードしない!
// ペイロードデータ
const payload = {
userId: 'user123',
username: 'taro_yamada',
roles: ['member']
};
// オプション (有効期限を1時間に設定)
const options = {
expiresIn: '1h', // 例: '15m', '7d'
issuer: 'https://my-service.com',
audience: 'https://api.my-service.com'
};
try {
// 1. JWTの生成 (HS256アルゴリズム)
const token = jwt.sign(payload, secretKey, options);
console.log('Generated Token:', token);
// --- ここから検証側 ---
// 2. JWTの検証
// 通常、Authorizationヘッダーからトークンを取得する
// const receivedToken = req.headers.authorization.split(' ')[1];
const receivedToken = token; // この例では生成したトークンを直接使う
const decoded = jwt.verify(receivedToken, secretKey, {
audience: 'https://api.my-service.com' // audienceも検証
});
console.log('Decoded Payload:', decoded);
// 検証成功! decoded.userId などを使って処理を進める
} catch (err) {
// 検証失敗 (有効期限切れ、署名不一致など)
console.error('JWT Verification Failed:', err.message);
// エラー処理 (例: 401 Unauthorizedを返す)
}
Python の例 (PyJWT
ライブラリ)
Bash
pip install pyjwt cryptography # RS/ES系を使う場合はcryptographyも必要
Python
import jwt
import time
from datetime import datetime, timedelta, timezone
# 秘密鍵 (環境変数などから読み込むのが安全)
secret_key = 'your-very-strong-secret-key' # ★絶対にハードコードしない!
algorithm = 'HS256'
# ペイロードデータ
payload = {
'userId': 'user456',
'username': 'hanako_sato',
'roles': ['admin', 'member'],
'iss': 'https://my-service.com',
'aud': 'https://api.my-service.com',
# 有効期限を1時間に設定 (UTC)
'exp': datetime.now(timezone.utc) + timedelta(hours=1),
'iat': datetime.now(timezone.utc)
}
try:
# 1. JWTの生成
token = jwt.encode(payload, secret_key, algorithm=algorithm)
print(f"Generated Token: {token}")
# --- ここから検証側 ---
# 2. JWTの検証
# 通常、Authorizationヘッダーからトークンを取得する
# received_token = request.headers.get('Authorization').split()[1]
received_token = token # この例では生成したトークンを直接使う
decoded_payload = jwt.decode(
received_token,
secret_key,
algorithms=[algorithm], # 検証するアルゴリズムを明示的に指定
audience='https://api.my-service.com' # audienceも検証
)
print(f"Decoded Payload: {decoded_payload}")
# 検証成功! decoded_payload['userId'] などを使って処理を進める
except jwt.ExpiredSignatureError:
print("Token has expired")
# エラー処理
except jwt.InvalidAudienceError:
print("Invalid audience")
# エラー処理
except jwt.InvalidTokenError as e:
print(f"Invalid token: {e}")
# エラー処理 (署名不一致など)
これらのライブラリを使うことで、JWTの生成と検証を比較的簡単に行うことができます。ただし、ライブラリのドキュメントをよく読み、セキュリティに関するオプション(アルゴリズム指定、有効期限、audience検証など)を正しく設定することが非常に重要です。
その他の言語の主要ライブラリ:
- Java:
jjwt
,Nimbus JOSE + JWT
- PHP:
firebase/php-jwt
,lcobucci/jwt
- Go:
dgrijalva/jwt-go
(アーカイブ済み、代替フォークあり),golang-jwt/jwt
- Ruby:
jwt
- C#:
System.IdentityModel.Tokens.Jwt
まとめ:JWTを正しく理解して安全に活用しよう!
今回は、JWT(JSON Web Token)について、その基本概念から構造、利用例、セキュリティまで、詳しく解説しました。
重要なポイントのおさらい:
- JWTは、JSON形式で情報を表現し、署名によって改ざんを検知できるトークン。
- ヘッダー、ペイロード、署名の3部構成。
- ステートレスな認証・認可に適しており、API連携やマイクロサービスで広く利用される。
- ペイロードは暗号化されていないため、機密情報を含めてはいけない。
- 署名は改ざん検知の要。適切なアルゴリズムと厳重な鍵管理が不可欠。
- 有効期限(
exp
)は必ず設定し、短めにする。 - HTTPS通信が必須。XSS対策やトークン失効戦略(リフレッシュトークンなど)も重要。
alg: "none"
は絶対に使用しない。
JWTは非常に強力で便利な技術ですが、その仕組みとセキュリティリスクを正しく理解せずに使うのは危険です。この記事が、あなたがJWTを安全かつ効果的に活用するための一助となれば幸いです。
より深く学びたい方は、RFC 7519の原文や、各言語のライブラリドキュメント、OWASPのチートシートなどを参照することをお勧めします。
セキュアでモダンなWeb開発のために、JWTの知識をぜひ役立ててください!
たび友|サイトマップ
関連webアプリ
たび友|サイトマップ:https://tabui-tomo.com/sitemap
索友:https://kentomo.tabui-tomo.com
ピー友:https://pdftomo.tabui-tomo.com
パス友:https://passtomo.tabui-tomo.com