コンテンツにスキップ

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
  1. Discovery{issuer}/.well-known/openid-configuration からOIDC Discoveryドキュメントを取得
  2. JWKSフェッチ — Discoveryドキュメント内の jwks_uri からJWKSを取得
  3. 鍵インポート — 各JWKを署名検証用のプラットフォームネイティブCryptoKeyとしてインポート
  4. キャッシュ — 鍵セットをメモリ(またはカスタムキャッシュプロバイダー)に保存

キャッシュの動作

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
});
設定デフォルト説明
jwksRefreshIntervalMs3600000(1時間)JWKSの再フェッチ間の最小間隔

キャッシュの優先順位

有効なキャッシュ期間は以下の順序で決定されます:

  1. Cache-Controlヘッダー — JWKSレスポンスのヘッダー(存在する場合)
  2. jwksRefreshIntervalMs — 最小間隔
  3. 24時間の上限 — 最大値

キャッシュの無効化

invalidateJwksCache() でJWKSの強制更新を行います:

// Force the SDK to refetch JWKS on the next validation
authrim.invalidateJwksCache();

これは以下の場合に有用です:

  • 鍵がローテーションされた通知を受け取った場合
  • 古い鍵が原因と思われる署名検証の失敗を検出した場合
  • 手動の鍵ローテーションワークフローを実装している場合

無効化後、次の validateToken() 呼び出しで新しいJWKSフェッチがトリガーされます。

鍵ローテーションのサポート

SDKは鍵ローテーションを自動的に処理します。トークンがキャッシュされたJWKSにない kid(Key ID)を参照している場合、SDKは:

  1. キャッシュが古くなっているか確認(jwksRefreshIntervalMs を超えているか)
  2. 古い場合、新しいJWKSを取得してルックアップを再試行
  3. 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-configuration
const authrim = new AuthrimServer({
issuer: 'https://auth.example.com',
audience: 'https://api.example.com',
});
// Explicit JWKS URI — skips discovery, fetches directly
const 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の ne)が欠落したJWKはスキップされる
  • サポートされていないアルゴリズム — サポートリストにない alg 値を持つJWKはスキップされる

スキップされた鍵は、セット内の他の鍵で署名されたトークンの検証に影響しません。

JWKSエラータイプ

エラー説明
JwksErrorJWKSのフェッチまたは処理に関する一般的なエラー
JwksKeyNotFoundError更新後もトークンの kid に一致する鍵が見つからない
JwksFetchErrorJWKSエンドポイントのフェッチ時のネットワークエラー
JwksRedirectErrorクロスオリジンリダイレクトの検出(SSRF保護)

次のステップ