イントロスペクションと失効
JWTアクセストークンは公開鍵を使用してローカルで検証できますが、一部のシナリオでは認可サーバーとの通信が必要です:
- トークンイントロスペクション(RFC 7662) — トークンが現在有効かどうかを認可サーバーに問い合わせる
- トークン失効(RFC 7009) — 認可サーバーにトークンの無効化を指示する
両方の機能は clientCredentials(コンフィデンシャルクライアント)が必要です。これは認可サーバーが呼び出し元を認証するためです。
イントロスペクションまたは失効を使用するには、エンドポイントとクライアント資格情報を設定します:
import { createAuthrimServer } from '@authrim/server';
const authrim = createAuthrimServer({ issuer: 'https://auth.example.com', audience: 'https://api.example.com',
// Introspection endpoint introspectionEndpoint: 'https://auth.example.com/oauth/introspect',
// Revocation endpoint revocationEndpoint: 'https://auth.example.com/oauth/revoke',
// Required for both introspection and revocation clientCredentials: { clientId: 'my-resource-server', clientSecret: 'resource-server-secret', },});
await authrim.init();SDKはこれらのエンドポイントを呼び出す際、クライアント資格情報を使用してHTTP Basic認証を行います。
トークンイントロスペクション(RFC 7662)
Section titled “トークンイントロスペクション(RFC 7662)”トークンイントロスペクションでは、認可サーバーにトークンの現在の状態を問い合わせることができます。これは特に以下の場合に有用です:
- 不透明トークン — 自己完結型のJWTではなく、ローカルで検証できないトークン
- リアルタイムの有効性チェック — 失効済みまたは期限切れのトークンを即座に検出
- トークンメタデータ — JWTに含まれていないクレームの取得
introspect API
Section titled “introspect API”const response = await authrim.introspect(token, tokenTypeHint?, issuerOptions?);| パラメータ | 型 | 説明 |
|---|---|---|
token | string | イントロスペクトするアクセストークン |
tokenTypeHint | 'access_token' | 'refresh_token' | 'device_secret' | オプションのtoken type hint |
issuerOptions | { issuer?: string; token?: ValidatedToken } | multi-issuer構成で使うオプションのissuer selector |
IntrospectionResponse
Section titled “IntrospectionResponse”interface IntrospectionResponse { active: boolean; sub?: string; scope?: string; client_id?: string; username?: string; token_type?: string; exp?: number; iat?: number; nbf?: number; aud?: string | string[]; iss?: string; jti?: string; cnf?: { jkt?: string }; installation_id?: string; app_display_name?: string; platform?: string; display_name?: string; fallback_display_name?: string; [key: string]: unknown; // Additional claims}最も重要なフィールドは active です:
active: true— トークンは有効で現在アクティブactive: false— トークンは無効、期限切れ、失効済み、または不明
const response = await authrim.introspect(accessToken);
if (!response.active) { // Token is not valid — reject the request return res.status(401).json({ error: 'invalid_token' });}
// Token is active — use the claimsconsole.log('Subject:', response.sub);console.log('Scopes:', response.scope);console.log('Client:', response.client_id);アクティブ vs 非アクティブの処理
Section titled “アクティブ vs 非アクティブの処理”イントロスペクションレスポンスの active: false は複数のシナリオをカバーします:
| シナリオ | active の値 | 備考 |
|---|---|---|
| 有効なトークン | true | トークンは現在有効で使用可能 |
| 期限切れトークン | false | トークンの exp が過ぎている |
| 失効済みトークン | false | トークンは明示的に失効された |
| 不明なトークン | false | このサーバーが発行したトークンではない |
| 不正な形式のトークン | false | トークンを解析できない |
スコープチェック付きイントロスペクション
Section titled “スコープチェック付きイントロスペクション”const response = await authrim.introspect(accessToken);
if (!response.active) { return res.status(401).json({ error: 'invalid_token' });}
// Check scopes manually for introspected tokensconst grantedScopes = (response.scope ?? '').split(' ');const requiredScopes = ['read:orders', 'write:orders'];
const hasAllScopes = requiredScopes.every( (scope) => grantedScopes.includes(scope));
if (!hasAllScopes) { return res.status(403).json({ error: 'insufficient_scope' });}トークン失効(RFC 7009)
Section titled “トークン失効(RFC 7009)”トークン失効は、認可サーバーに特定のトークンの無効化を指示します。失効後、そのトークンは認可サーバーやイントロスペクションを使用するリソースサーバーでは受け入れられなくなります。
revoke API
Section titled “revoke API”await authrim.revoke(token, tokenTypeHint?, issuerOptions?);| パラメータ | 型 | 説明 |
|---|---|---|
token | string | 失効するトークン |
tokenTypeHint | 'access_token' | 'refresh_token' | 'device_secret' | オプションのtoken type hint |
issuerOptions | { issuer?: string; token?: ValidatedToken } | multi-issuer構成で使うオプションのissuer selector |
try {await authrim.revoke(accessToken, 'access_token'); console.log('Token revoked successfully');} catch (error) { console.error('Revocation failed:', error.message);}Native SSO device_secret
Section titled “Native SSO device_secret”Native SSO device_secret は同じイントロスペクション・失効エンドポイントを device_secret token type hint付きで使います。hintの指定漏れを避けるため、専用helperを優先してください。
const response = await authrim.introspectDeviceSecret(deviceSecret, { issuer: 'https://auth.example.com',});
if (response.active) { console.log(response.installation_id); console.log(response.platform);}
await authrim.revokeDeviceSecret(deviceSecret, { issuer: 'https://auth.example.com',});raw device_secret をログに出さないでください。refresh token相当のcredentialとして扱います。
失効のユースケース
Section titled “失効のユースケース”- ログアウト — ユーザーがサインアウトする際にアクセストークンを失効
- セキュリティインシデント — 侵害されたトークンを即座に無効化
- 権限変更 — ユーザーの権限が更新された際にトークンを失効(再認証を強制)
// Example: Revoke on logoutapp.post('/api/logout', auth(), async (req, res) => { const token = req.authrim!.token.token;
// Revoke the access token at the authorization server await authrim.revoke(token, 'access_token');
res.json({ message: 'Logged out' });});ローカル検証 vs イントロスペクション
Section titled “ローカル検証 vs イントロスペクション”ローカルJWT検証とトークンイントロスペクションの選択は、要件によって異なります:
| 基準 | ローカル検証 | イントロスペクション |
|---|---|---|
| トークン形式 | JWTのみ | JWTまたは不透明 |
| ネットワーク呼び出し | なし(キャッシュされたJWKSを使用) | あり(毎回) |
| レイテンシ | ローカルの暗号検証とキャッシュ参照 | 認可サーバーへのネットワーク往復 |
| 失効の検出 | 不可 | 可能 |
| リアルタイムの有効性 | 不可(exp に依存) | 可能 |
| クライアント資格情報が必要 | 不要 | 必要 |
| オフライン動作 | 可能(JWKSキャッシュ後) | 不可 |
flowchart TD
A["トークン受信"] --> B{"トークン形式?"}
B -->|JWT| C{"リアルタイムの<br>失効チェックが必要?"}
B -->|不透明| D["イントロスペクション"]
C -->|はい| E["イントロスペクション"]
C -->|いいえ| F{"機密操作?"}
F -->|はい| G["両方を使用:<br>ローカル + イントロスペクション"]
F -->|いいえ| H["ローカル検証"]
ハイブリッドアプローチ
Section titled “ハイブリッドアプローチ”機密操作では、ローカル検証とイントロスペクションを組み合わせます:
// Fast path: Local JWT validationconst tokenResult = await authrim.validateToken(accessToken);
// For sensitive operations, also check with the authorization serverif (isSensitiveOperation(req.path)) { const introspectionResult = await authrim.introspect(accessToken); if (!introspectionResult.active) { return res.status(401).json({ error: 'invalid_token', error_description: 'Token has been revoked', }); }}これにより両方の利点を得られます:
- ほとんどのリクエストに対する高速な検証(ローカルJWT検証)
- 重要な操作に対するリアルタイムの有効性(イントロスペクション)
イントロスペクション結果のキャッシュ
Section titled “イントロスペクション結果のキャッシュ”高スループットAPIでは、イントロスペクション結果を短期間キャッシュできます:
const INTROSPECTION_CACHE_TTL = 30_000; // 30 seconds
async function introspectWithCache(token: string) { const cacheKey = `introspect:${hashToken(token)}`;
// Check cache first const cached = await cache.get(cacheKey); if (cached) return cached;
// Call authorization server const result = await authrim.introspect(token);
// Cache the result (short TTL) if (result.active) { await cache.set(cacheKey, result, INTROSPECTION_CACHE_TTL); }
return result;}エラータイプ
Section titled “エラータイプ”| エラー | 説明 |
|---|---|
IntrospectionError | イントロスペクションエンドポイントの呼び出しに失敗 |
RevocationError | 失効エンドポイントの呼び出しに失敗 |
ClientCredentialsError | クライアント資格情報の欠落または無効 |
NetworkError | ネットワーク接続の問題 |
次のステップ
Section titled “次のステップ”- トークン検証 — ローカルJWT検証パイプライン
- DPoP検証 — 送信者制約付きトークンの検証
- JWKS管理 — 鍵のキャッシュとローテーション
- セキュリティに関する考慮事項 — 本番環境のセキュリティチェックリスト