JWKS管理
概要
@authrim/server はJSON Web Key Sets (JWKS) を自動的に管理します。認可サーバーの公開鍵を検出し、効率的にキャッシュし、鍵のローテーションを処理します。これらはすべて手動の介入なしに行われます。
JWKSのライフサイクルを理解することで、パフォーマンスの調整や本番環境での鍵関連の問題のトラブルシューティングに役立ちます。
JWKS自動検出の仕組み
authrim.init() を呼び出すと、SDKはOIDC DiscoveryからJWKSエンドポイントを特定します:
sequenceDiagram
participant SDK as @authrim/server
participant Issuer as Authorization Server
SDK->>Issuer: GET /.well-known/openid-configuration
Issuer-->>SDK: { "jwks_uri": "https://auth.example.com/jwks" }
SDK->>Issuer: GET /jwks
Issuer-->>SDK: { "keys": [ { "kid": "key-1", ... } ] }
SDK->>SDK: Import and cache public keys
- Discovery —
{issuer}/.well-known/openid-configurationからOIDC Discoveryドキュメントを取得 - JWKSフェッチ — Discoveryドキュメント内の
jwks_uriからJWKSを取得 - 鍵インポート — 各JWKを署名検証用のプラットフォームネイティブCryptoKeyとしてインポート
- キャッシュ — 鍵セットをメモリ(またはカスタムキャッシュプロバイダー)に保存
キャッシュの動作
SDKはトークン検証のたびに鍵を取得しなくて済むよう、JWKSをキャッシュします。キャッシュは3つのメカニズムで制御されます:
Cache-Controlヘッダー
SDKはJWKSエンドポイントレスポンスの Cache-Control ヘッダーに従います。認可サーバーが Cache-Control: max-age=3600 を返した場合、SDKは3600秒間再フェッチを行いません。
SDKは Cache-Control の値に関係なく、最大キャッシュ期間を24時間に制限しています。これにより、サーバーが非常に長いmax-ageを返した場合でも、鍵は最終的に更新されます。
jwksRefreshIntervalMs
jwksRefreshIntervalMs 設定は、JWKSフェッチ間の最小間隔を設定します。鍵が見つからない場合でも、SDKはこの間隔より頻繁には再フェッチしません。
const authrim = new AuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com', jwksRefreshIntervalMs: 1800000, // 30 minutes});| 設定 | デフォルト | 説明 |
|---|---|---|
jwksRefreshIntervalMs | 3600000(1時間) | JWKSの再フェッチ間の最小間隔 |
キャッシュの優先順位
有効なキャッシュ期間は以下の順序で決定されます:
- Cache-Controlヘッダー — JWKSレスポンスのヘッダー(存在する場合)
- jwksRefreshIntervalMs — 最小間隔
- 24時間の上限 — 最大値
キャッシュの無効化
invalidateJwksCache() でJWKSの強制更新を行います:
// Force the SDK to refetch JWKS on the next validationauthrim.invalidateJwksCache();これは以下の場合に有用です:
- 鍵がローテーションされた通知を受け取った場合
- 古い鍵が原因と思われる署名検証の失敗を検出した場合
- 手動の鍵ローテーションワークフローを実装している場合
無効化後、次の validateToken() 呼び出しで新しいJWKSフェッチがトリガーされます。
鍵ローテーションのサポート
SDKは鍵ローテーションを自動的に処理します。トークンがキャッシュされたJWKSにない kid(Key ID)を参照している場合、SDKは:
- キャッシュが古くなっているか確認(
jwksRefreshIntervalMsを超えているか) - 古い場合、新しいJWKSを取得してルックアップを再試行
kidがまだ見つからない場合、JwksErrorをスロー
この自動リトライメカニズムは、認可サーバーがリソースサーバーのキャッシュが期限切れになる前に新しい鍵で署名を開始する一般的なローテーションシナリオに対応しています。
flowchart TD
A["kid: 'key-2' の
トークン"] --> B{"kidがキャッシュに
存在する?"}
B -->|はい| C["キャッシュされた
鍵を使用"]
B -->|いいえ| D{"キャッシュが
古い?"}
D -->|はい| E["新しいJWKSを
取得"]
D -->|いいえ| F["JwksErrorを
スロー
(kid未検出)"]
E --> G{"新しいJWKSに
kidが存在する?"}
G -->|はい| H["新しい鍵を使用"]
G -->|いいえ| F
Single-Flightパターン
複数のリクエストが同時に到着しJWKSの更新をトリガーした場合、SDKはそれらを単一のネットワークリクエストに集約します。これにより「thundering herd」問題を防止します:
sequenceDiagram
participant R1 as リクエスト 1
participant R2 as リクエスト 2
participant R3 as リクエスト 3
participant SDK as @authrim/server
participant Auth as 認可サーバー
R1->>SDK: validateToken() — kid未検出
R2->>SDK: validateToken() — kid未検出
R3->>SDK: validateToken() — kid未検出
SDK->>Auth: GET /jwks(単一リクエスト)
Auth-->>SDK: { "keys": [...] }
SDK-->>R1: 検証結果
SDK-->>R2: 検証結果
SDK-->>R3: 検証結果
3つのリクエストすべてが同じJWKSフェッチを待ち、結果を共有します。これにより:
- 認可サーバーへの負荷を軽減
- 同時検証間での一貫した鍵状態を保証
- レイテンシを最小化(ラウンドトリップは1回のみ)
SSRFからの保護
カスタムCacheProvider
マルチサーバーデプロイメントでは、分散キャッシュを使用してインスタンス間でJWKSを共有することをお勧めします。CacheProvider インターフェースを実装してください:
import type { CacheProvider } from '@authrim/server/providers';import type { JwkSet } from '@authrim/server';
const redisCache: CacheProvider<JwkSet> = { async get(key: string): Promise<JwkSet | undefined> { const data = await redis.get(key); if (!data) return undefined; return JSON.parse(data) as JwkSet; },
async set(key: string, value: JwkSet, ttlMs: number): Promise<void> { await redis.set(key, JSON.stringify(value), 'PX', ttlMs); },
async delete(key: string): Promise<void> { await redis.del(key); },};
const authrim = new AuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com', jwksCache: redisCache,});共有キャッシュの利点:
- JWKSフェッチの削減 — 1台のサーバーがフェッチすれば、全サーバーが恩恵を受ける
- コールドスタートの高速化 — 新しいインスタンスがキャッシュされた鍵を即座に取得
- 一貫した鍵状態 — すべてのサーバーが同時に同じ鍵を参照
明示的なjwksUri vs 自動検出
デフォルトでは、SDKはOIDC Discoveryドキュメントからの情報でJWKSエンドポイントを検出します。明示的な jwksUri でこれをオーバーライドできます:
// Auto-discovery (default) — fetches from .well-known/openid-configurationconst authrim = new AuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',});
// Explicit JWKS URI — skips discovery, fetches directlyconst authrim = new AuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com', jwksUri: 'https://auth.example.com/.well-known/jwks.json',});明示的な jwksUri を使用する場面:
- 認可サーバーがOIDC Discoveryをサポートしていない場合
- 起動時間を短縮するために初期のDiscoveryリクエストを避けたい場合
- 非標準のJWKSエンドポイントパスを使用している場合
鍵インポートの警告
SDKは鍵のインポート時に予期しないパラメータに遭遇した場合、警告を出力することがあります。これらの警告は情報提供目的であり、鍵の使用を妨げるものではありません:
- 不明な鍵タイプ — 認識されない
kty値を持つJWKはスキップされる - 必須パラメータの欠落 — 必須フィールド(例:RSAの
nとe)が欠落したJWKはスキップされる - サポートされていないアルゴリズム — サポートリストにない
alg値を持つJWKはスキップされる
スキップされた鍵は、セット内の他の鍵で署名されたトークンの検証に影響しません。
JWKSエラータイプ
| エラー | 説明 |
|---|---|
JwksError | JWKSのフェッチまたは処理に関する一般的なエラー |
JwksKeyNotFoundError | 更新後もトークンの kid に一致する鍵が見つからない |
JwksFetchError | JWKSエンドポイントのフェッチ時のネットワークエラー |
JwksRedirectError | クロスオリジンリダイレクトの検出(SSRF保護) |
次のステップ
- トークン検証 — JWKSを使用するJWT検証パイプライン
- DPoP検証 — JWK ThumbprintによるDPoP証明の検証
- セキュリティに関する考慮事項 — SSRF保護と本番環境のセキュリティガイダンス
- イントロスペクションと失効 — ローカルJWT検証の代替手段