Google Cloud Pub/Sub の Push サブスクリプションからの Webhook を JWT で認証する方法。Google が推奨するセキュリティ対策として、従来のトークン検証から JWT 検証へ移行する。
実装手順:
- 必要なパッケージをインストール
pnpm add jsonwebtoken jwks-rsa
pnpm add -D @types/jsonwebtoken
- JWT 検証サービスの実装
import * as jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
// Google の公開鍵を取得するクライアント
const client = jwksClient({
jwksUri: 'https://www.googleapis.com/oauth2/v3/certs',
cache: true,
cacheMaxAge: 10 * 60 * 1000, // 10分間キャッシュ
rateLimit: true,
jwksRequestsPerMinute: 10,
});
// JWT 検証
async verifyPubSubJWT(token: string, expectedAudience: string) {
const decoded = jwt.decode(token, { complete: true });
const key = await client.getSigningKey(decoded.header.kid);
const publicKey = key.getPublicKey();
const verified = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
audience: expectedAudience, // Webhook エンドポイントの完全 URL
issuer: ['https://accounts.google.com', 'accounts.google.com'],
});
// email_verified の確認も必須
if (!verified.email_verified) {
throw new Error('Email not verified');
}
return verified;
}
- Webhook エンドポイントでの検証
// Authorization ヘッダーから Bearer トークンを取得
const authHeader = request.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
throw new Error('JWT authentication required');
}
const jwtToken = authHeader.substring(7);
const audience = `https://${request.headers.host}/webhooks/gmail`;
await verifyPubSubJWT(jwtToken, audience);
重要なポイント:
- audience は Webhook URL と完全一致が必要
- issuer は Google のアカウントサービス
- email_verified が true であることを確認
- 公開鍵は kid でキャッシュから取得