JWTトークンを理解する:完全ガイド
· 12分で読む
目次
JSON Web Token(JWT)とは?
JSON Web Token(JWT)は、現代のWebアプリケーションにおいて、当事者間で情報を安全に送信するための事実上の標準となっています。RFC 7519で定義されているJWTは、2つの当事者間で転送されるクレームを表現するための、コンパクトでURLセーフな方法を提供します。
サーバーが状態を維持する従来のセッションベース認証とは異なり、JWTは自己完結型です。つまり、トークン自体がユーザーの身元と権限を検証するために必要なすべての情報を保持しています。このステートレスな性質により、JWTは分散システム、マイクロサービスアーキテクチャ、モバイルアプリケーションにおいて特に価値があります。
JWTは現代のアプリケーションにおいて2つの主要な目的を果たします:
- 認可: ユーザーがログインすると、その後の各リクエストにJWTが含まれ、ユーザーはそのトークンで許可されたルート、サービス、リソースにアクセスできます。シングルサインオン(SSO)の実装は、小さなオーバーヘッドとクロスドメイン機能により、JWTに大きく依存しています。
- 情報交換: JWTは当事者間で情報を送信する安全な方法を提供します。JWTは公開鍵/秘密鍵ペアを使用して署名できるため、送信者が主張する本人であることと、コンテンツが改ざんされていないことを検証できます。
JWTの美しさは、そのシンプルさと汎用性にあります。異なるプログラミング言語やプラットフォーム間でシームレスに動作するため、フロントエンドがReact、バックエンドがNode.js、モバイルアプリがSwiftやKotlinといった異種環境に最適です。
プロのヒント: JWTは非常に便利ですが、すべての認証シナリオに対する万能薬ではありません。JWTと従来のセッションをいつ使用するかを理解することは、安全でスケーラブルなアプリケーションを構築する上で重要です。
JWTの詳細な構造と解剖
JWTは、ドット(.)で区切られた3つの異なる部分で構成され、xxxxx.yyyyy.zzzzzのような構造を形成します。各セクションは特定の目的を果たし、一緒になって改ざん防止された情報パッケージを作成します。
ヘッダー
ヘッダーは通常、トークンのタイプ(JWT)と使用されている署名アルゴリズム(HMAC SHA256やRSAなど)の2つの部分で構成されます。この情報は、受信側にトークンの署名を検証する方法を伝えます。
{
"alg": "HS256",
"typ": "JWT"
}
ヘッダーはBase64Urlエンコードされ、JWTの最初の部分を形成します。よく遭遇する一般的なアルゴリズムには以下があります:
- HS256(HMAC with SHA-256): 共有秘密を使用する対称アルゴリズム
- RS256(RSA Signature with SHA-256): 公開鍵/秘密鍵ペアを使用する非対称アルゴリズム
- ES256(ECDSA with SHA-256): 楕円曲線暗号を使用する非対称アルゴリズム
ペイロード
ペイロードには、エンティティ(通常はユーザー)に関するステートメントと追加のメタデータであるクレームが含まれます。クレームは3つのタイプに分類されます:
登録済みクレーム: これらは必須ではありませんが、相互運用性を提供するために推奨される事前定義されたクレームです。以下が含まれます:
iss(発行者): トークンを発行した者を識別sub(サブジェクト): トークンのサブジェクト(通常はユーザーID)を識別aud(オーディエンス): JWTが対象とする受信者を識別exp(有効期限): JWTが期限切れになるタイムスタンプnbf(not before): JWTが受け入れられてはならない前のタイムスタンプiat(発行時刻): JWTが発行されたタイムスタンプjti(JWT ID): JWTの一意識別子
公開クレーム: これらはアプリケーション用に定義するカスタムクレームです。衝突を避けるため、IANA JSON Web Tokenレジストリで定義するか、衝突耐性のある名前(名前空間付きURIなど)を使用する必要があります。
プライベートクレーム: 使用に同意した当事者間で情報を共有するために作成されたカスタムクレーム。これらは登録済みクレームでも公開クレームでもありません。
{
"sub": "1234567890",
"name": "John Doe",
"email": "[email protected]",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
ペイロードはBase64Urlエンコードされ、JWTの2番目の部分を形成します。
クイックヒント: パスワードやクレジットカード番号などの機密情報をJWTペイロードに保存しないでください。JWTは署名されていますが、デフォルトでは暗号化されていないため、誰でもペイロードをデコードして読むことができます。
署名
署名は、エンコードされたヘッダー、エンコードされたペイロード、秘密(HMACアルゴリズムの場合)または秘密鍵(RSA/ECDSAの場合)を取り、ヘッダーで指定されたアルゴリズムを使用して署名することで作成されます。
例えば、HMAC SHA256を使用する場合、署名は次のように作成されます:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
署名は、トークンが変更されていないことを保証します。誰かがヘッダーやペイロードの1文字でも変更すると、署名検証は失敗します。
完全なJWTの例
3つの部分をすべて組み合わせると、次のような完全なJWTが得られます:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWTデコーダーツールを使用して、任意のJWTをデコードして検査し、そのヘッダー、ペイロードを確認し、署名を検証できます。
実践におけるJWTの作成と活用
適切なライブラリを使用すれば、JWTの作成は簡単です。さまざまなプログラミング言語とシナリオでJWTを生成して使用する方法を見ていきましょう。
Node.jsでのJWT作成
jsonwebtokenライブラリは、Node.jsアプリケーションで最も人気のある選択肢です:
const jwt = require('jsonwebtoken');
// トークンを作成
const payload = {
sub: user.id,
email: user.email,
role: user.role
};
const secret = process.env.JWT_SECRET;
const options = {
expiresIn: '1h',
issuer: 'myapp.com'
};
const token = jwt.sign(payload, secret, options);
// トークンを検証
try {
const decoded = jwt.verify(token, secret);
console.log(decoded);
} catch (error) {
console.error('無効なトークン:', error.message);
}
PythonでのJWT作成
Python開発者は通常、PyJWTライブラリを使用します:
import jwt
import datetime
# トークンを作成
payload = {
'sub': user.id,
'email': user.email,
'role': user.role,
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1),
'iat': datetime.datetime.utcnow()
}
secret = os.environ.get('JWT_SECRET')
token = jwt.encode(payload, secret, algorithm='HS256')
# トークンを検証
try:
decoded = jwt.decode(token, secret, algorithms=['HS256'])
print(decoded)
except jwt.ExpiredSignatureError:
print('トークンの有効期限が切れています')
except jwt.InvalidTokenError:
print('無効なトークン')
JavaでのJWT作成
Javaアプリケーションでは、Auth0のjava-jwtライブラリが広く使用されています:
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
// トークンを作成
Algorithm algorithm = Algorithm.HMAC256(secret);
String token = JWT.create()
.withSubject(user.getId())
.withClaim("email", user.getEmail())
.withClaim("role", user.getRole())
.withExpiresAt(new Date(System.currentTimeMillis() + 3600000))
.withIssuer("myapp.com")
.sign(algorithm);
// トークンを検証
try {
DecodedJWT jwt = JWT.require(algorithm)
.withIssuer("myapp.com")
.build()
.verify(token);
} catch (Exception e) {
System.out.println("無効なトークン: " + e.getMessage());
}
トークン生成のベストプラクティス
JWTを作成する際は、セキュリティと信頼性を確保するために以下のガイドラインに従ってください:
- 強力な秘密を使用: JWT秘密は少なくとも256ビット(32文字)のランダムデータである必要があります。ソースコードに秘密をハードコードしないでください。
- 適切な有効期限を設定: アクセストークンは短命(15分から1時間)にし、リフレッシュトークンはより長く(数日から数週間)できます。
- 必要なクレームのみを含める: トークンサイズを削減し、情報の露出を最小限に抑えるために、ペイロードを軽量に保ちます。
- 適切なアルゴリズムを使用: ほとんどのアプリケーションでは、HS256で十分です。秘密を共有せずにトークンを検証する必要がある場合(マイクロサービスなど)は、RS256を使用してください。
プロのヒント: リフレッシュトークンを使用したトークンリフレッシュメカニズムの実装を検討してください。これにより、頻繁な再認証なしに良好なユーザーエクスペリエンスを維持しながら、セキュリティのためにアクセストークンを短命に保つことができます。
JWT認証ワークフローの説明
JWTがアプリケーション内でどのように流れるかを理解することは、正しく実装するために重要です。典型的な認証ワークフローをステップバイステップで見ていきましょう。
初期認証
- ユーザーが資格情報を送信: ユーザーはユーザー名とパスワードを認証エンドポイント(通常は
POST /api/auth/login)に送信します。 - サーバーが資格情報を検証: サーバーはデータベースに対して資格情報をチェックし、パスワードハッシュが一致することを確認します。
- サーバーがJWTを生成: 検証が成功すると、サーバーはユーザーの身元と関連するクレームを含むJWTを作成します。
- サーバーがトークンを返す: JWTはクライアントに返され、通常はレスポンスボディに含まれます。一部の実装では、HTTP-onlyクッキーとして設定されることもあります。
後続のリクエスト
- クライアントがトークンを含める: 後続の各リクエストで、クライアントはAuthorizationヘッダーにJWTを含めます:
Authorization: Bearer <token> - サーバーがトークンを検証: サーバーはトークンを抽出し、その署名を検証し、有効期限をチェックします。
- サーバーがリクエストを処理: トークンが有効な場合、サーバーはペイロードからユーザー情報を抽出し、それに応じてリクエストを処理します。
- サーバーがレスポンスを返す: 要求されたデータまたは操作結果がクライアントに返されます。
トークンリフレッシュフロー
アクセストークンの有効期限が切れると、リフレッシュフローが開始されます:
- クライアントが有効期限切れを検出: クライアントは401 Unauthorizedレスポンスを受け取るか、トークンの
expクレームをチェックします。 - クライアントがリフレッシュトークンを送信: クライアントは専用のリフレッシュエンドポイント(
POST /api/auth/refresh)にリフレッシュトークンを送信します。 - サーバーがリフレッシュトークンを検証: サーバーはリフレッシュトークンを検証し、取り消されていないかチェックします。
- サーバーが新しいアクセストークンを発行: 新しいアクセストークンが生成され、クライアントに返されます。
- クライアントが元のリクエストを再試行: クライアントは新しいアクセストークンを使用して、失敗したリクエストを再試行します。
ログアウトフロー
JWTでのログアウトは、トークンがステートレスであるため、特別な考慮が必要です:
- クライアントがログアウトを開始: ユーザーがログアウトをクリックし、
POST /api/auth/logoutへのリクエストをトリガーします。 - サーバーがリフレッシュトークンを無効化: サーバーはリフレッシュトークンを取り消しリストに追加するか、データベースから削除します。
- クライアントがトークンを破棄: クライアントはアクセストークンとリフレッシュトークンの両方をストレージから削除します。
- クライアントがリダイレクト: ユーザーはログインページまたは公開エリアにリダイレクトされます。
| トークンタイプ | 典型的な有効期間 | 保存場所 | 目的 |
|---|---|---|---|
| アクセストークン | 15分 - 1時間 | メモリまたはsessionStorage | APIリクエストの認可 |
| リフレッシュトークン | 7日 - 30日 | HTTP-onlyクッキーまたはセキュアストレージ | 新しいアクセストークンの取得 |
| IDトークン | アクセストークンと同じ | メモリ | ユーザー識別情報(OpenID Connect) |
クイックヒント: クライアントアプリケーションに自動トークンリフレッシュを実装して、トークンの有効期限切れをシームレスに処理します。これにより、ユーザーが予期せずログアウトされることを防ぎます