- 1 1. この記事について
- 2 2. 前提・環境・準備
- 3 3. REST API vs HTTP API 選択基準 (コスト・機能・レイテンシ比較)
- 4 4. Lambda Authorizer 3種完全比較 (TOKEN / REQUEST / JWT)
- 5 5. Cognito Authorizer vs Lambda Authorizer 使い分け + サードパーティIdP統合
- 6 6. Terraform 実装 (aws_apigatewayv2_authorizer + aws_lambda_function 完全 HCL)
- 6.1 6-1. ディレクトリ構成 (modules/ + envs/)
- 6.2 6-2. aws_apigatewayv2_api + aws_apigatewayv2_stage
- 6.3 6-3. aws_apigatewayv2_authorizer 3種フル実装 (TOKEN/REQUEST/JWT)
- 6.4 6-4. aws_apigatewayv2_route + aws_apigatewayv2_integration
- 6.5 6-5. aws_lambda_function (Authorizer Lambda) + aws_iam_role
- 6.6 6-6. outputs.tf + variables.tf
- 6.7 6-7. 落とし穴 HCL 例 (よくある設定ミス)
- 7 7. Caching 戦略 + WAF 統合 + 観測性
- 7.1 7-1. Authorizer Result キャッシュ設計 (authorizerResultTtlInSeconds)
- 7.2 7-2. identitySource キャッシュキー設計
- 7.3 7-3. QG-4: Caching + identitySource 設計の重要ポイント
- 7.4 7-4. AWS WAF Web ACL + HTTP API 統合
- 7.5 7-5. WAF 紐付け順ミスの落とし穴
- 7.6 7-6. QG-5: 落とし穴10選 + WAF 統合まとめ
- 7.7 7-7. 観測性: CloudWatch Logs + X-Ray トレーシング + Access Log 形式
- 8 8. まとめ + よくある落とし穴10選 + 次回予告
1. この記事について

AWS が 2020 年に GA した HTTP API (apigatewayv2) は REST API (v1) と比べてコスト 70% 削減・低レイテンシ・JWT Authorizer 内蔵という三大メリットを持ちます。しかし本番運用で最初に壁となるのが「認証認可の設計」です。TOKEN / REQUEST / JWT の 3 種 Authorizer をどう使い分けるか、Cognito と Auth0 のどちらを選ぶか、Caching の identitySource キャッシュキーをどう設計するか——これらを体系化した日本語記事は少ない状況です。
本記事はその空白を 1 本で解消します。REST API との比較から始まり、3 種 Authorizer の完全実装・Cognito/Auth0 統合・Caching コスト設計・WAF 統合まで、Terraform 完全 HCL 付きで解説します。読者が「そのままコピー&ペーストで本番 HTTP API + Lambda Authorizer を構築できる」ことをゴールとしています。
1-1. なぜ今 HTTP API + Lambda Authorizer か
2020 年の HTTP API GA から 5 年以上が経ちましたが、認証認可まわりの日本語記事には 4 つの空白があります。
まず前提として、HTTP API の主なメリットを整理します。
| メリット | HTTP API (v2) | REST API (v1) |
|---|---|---|
| リクエスト料金 (最初の 300M/月) | $1.00/百万 | $3.50/百万 |
| JWT Authorizer 内蔵 (OIDC/OAuth 2.0) | ✓ Lambda 不要 | ✗ 要カスタム Lambda |
| p99 レイテンシ | 低い | やや高い |
| カスタムドメイン・マネージド CORS | ✓ | ✓ |
| API キー・使用量プラン | ✗ | ✓ |
空白①: Authorizer 3 種の横断比較がない
TOKEN / REQUEST / JWT の選択フロー図・実装コード・Terraform HCL・落とし穴を 3 種まとめて解説した記事が国内にほぼ存在しません。個別記事はあっても「なぜ TOKEN でなく JWT を選ぶのか」「REQUEST Authorizer を使うべき条件は何か」という判断軸を示したものが不足しています。
空白②: Cognito と Auth0 の並列比較がない
国内記事は「Cognito だけ」または「Auth0 だけ」で完結するものがほとんどです。issuer / audience / jwks_uri の設定を両 IdP で並列展開し、採用判断の軸を示した記事が見当たりません。
空白③: Caching コスト計算の実例がない
authorizerResultTtlInSeconds の設定値によって Lambda 呼び出し回数が最大 99.9% 削減される効果を月間コスト試算で示した記事がほとんどありません。identitySource キャッシュキー設計のミスが想定外コストに直結することも広く知られていません。
空白④: WAF + HTTP API 統合手順が散在
aws_wafv2_web_acl_association で HTTP API に WAF を紐付ける Terraform 実装は公式ドキュメントでも解説が分散しており、よくある「紐付け順ミス」の落とし穴を事前に学べる記事が見当たりません。
1-2. 本記事のゴール
本記事を読み終えると以下を単独で実施できます。
| ゴール | 対応章 |
|---|---|
| REST API と HTTP API の選択判断 (コスト・機能・レイテンシ 3 軸) | §3 |
| TOKEN / REQUEST / JWT Authorizer の Terraform 本番実装 | §4 / §6 |
| Cognito User Pools と Auth0 の JWT Authorizer 統合 | §5 |
| authorizerResultTtlInSeconds と identitySource キャッシュキー設計 | §7 |
| WAF Web ACL を HTTP API に紐付けて Rate Limit + Bot 対策 | §7 |
| CloudWatch Logs + X-Ray でリクエスト〜認可の観測性確保 | §7 |
1-3. 差別化6軸
- HTTP API 特化: REST API との比較は行うが、本番実装は HTTP API (apigatewayv2) に絞って深掘り。コスト計算・p99 改善の実感値を提示
- Authorizer 3種完全比較: TOKEN / REQUEST / JWT の選択フロー図・実装コード・Terraform HCL・落とし穴を全種揃えて横断比較 (§4)
- Cognito vs Auth0 二案並列: issuer / audience / jwks_uri 設定を両 IdP で並列展開し、採用判断の軸を提示 (§5)
- Caching 実測コスト差分: TTL=0/300/3600 の月間コスト試算と identitySource キャッシュキー設計の落とし穴 (§7)
- WAF 統合 Terraform 実装: aws_wafv2_web_acl_association で HTTP API に紐付ける完全 HCL と紐付け順ミスの解説 (§7)
- Terraform 完全実装セット: aws_apigatewayv2_api / _authorizer / _route / _integration + aws_lambda_function + IAM を 1 セットで HCL 化 (§6)
1-4. 読者像と前提知識
本記事は 中級 AWS エンジニア を対象とします。以下を前提知識として想定しています。
| 前提知識 | 必要レベル |
|---|---|
| AWS アカウント操作・IAM | ポリシー作成・ロール付与ができること |
| API Gateway REST API | Authorizer の概念を知っていること |
| Lambda | Python or Node.js でハンドラを書けること |
| Terraform | resource / variable / output が書けること |
| Cognito または Auth0 | JWT (iss/aud/sub) の概念を知っていること |
上記を満たしていない場合は REST API + Cognito の入門記事を先に参照することを推奨します。本記事は「なぜそうなるのか」よりも「どう実装するか」に重点を置いています。
1-5. この記事の読み方
すべての章を読む必要はありません。目的に応じて以下の読み方ができます。
| 目的 | 推奨章 |
|---|---|
| REST API から移行すべきか判断したい | §3 のみ |
| JWT Authorizer (Cognito/Auth0) をすぐ使いたい | §4-5-6 の JWT 部分 |
| Terraform で一式プロビジョニングしたい | §6 を中心に §4-5 を参照 |
| 既存 API に WAF を後付けしたい | §7 の WAF 統合節 |
| 落とし穴だけ確認したい | §8 の落とし穴10選 |
1-6. 関連記事との位置づけ
本記事は API Gateway + Lambda 認証認可の実装に特化した記事です。
- Cognito + Salesforce mTLS 連携: mTLS クライアント証明書による B2B 認証シナリオ (本記事の Lambda Authorizer とは別軸)
- Bedrock Agents 本番運用ガイド: Lambda Authorizer で保護した HTTP API を Action Group に利用するパターン
- CloudFront WAF Bot Control: CloudFront + WAF の詳細実装 (本記事§7 WAF 統合の上流設計)
AWS 公式: HTTP API Lambda Authorizer ドキュメント
リクエストフロー全体像 [Client] ──HTTPS──▶ [WAF Web ACL] ──PASS──▶ [HTTP API (apigatewayv2)] │ │ BLOCK│ ┌────────────────────────┐ ▼ │ Lambda Authorizer│ [403 Forbidden] │ TOKEN / REQUEST │◀──▶ [IdP] │ または JWT Authorizer │ Cognito │ (JWT = Lambda 不要)│ Auth0 └────────────────────────┘ │ Allow ◀────────────────▶ Deny │ │ ▼ ▼ [Backend Lambda] [403 Forbidden] (ビジネスロジック) Authorizer 種別選択指針: JWT Authorizer→ Cognito/Auth0 JWT 検証 (Lambda 不要・推奨・最速) TOKEN Authorizer → Bearer トークンをカスタム Lambda で検証 REQUEST Auth. → ヘッダ+クエリ+IP の複合条件で Lambda が判定
2. 前提・環境・準備

本章では検証に必要なローカル環境・AWS 権限・IAM 構成・Terraform ワークスペースを整備します。§3 以降のハンズオンはすべてこの環境を前提とします。
2-1. 前提環境
| ツール / サービス | バージョン | 用途 |
|---|---|---|
| AWS アカウント | — | HTTP API・Lambda・Cognito・WAF を作成 |
| AWS CLI | v2.15 以上 | リソース確認・CLI 手順実行 |
| Terraform | 1.9.x 以上 | IaC 実装 (aws_apigatewayv2_ / aws_lambda_) |
| Python | 3.11 以上 | Lambda Authorizer ロジック (TOKEN/REQUEST) |
| Node.js | 20.x 以上 | 代替 Lambda runtime (選択可) |
| cURL | 7.x 以上 | エンドポイントテスト |
| jq | 1.6 以上 | AWS CLI 出力パース |
AWS CLI の設定確認:
# IAM ユーザー/ロールを確認
aws sts get-caller-identity
# 出力例: { "Account": "123456789012", "Arn": "arn:aws:iam::123456789012:user/myuser" }
# API Gateway 操作権限確認
aws apigatewayv2 get-apis --query 'Items[0].ApiId' 2>&1 | head -1
# Lambda 操作権限確認
aws lambda list-functions --query 'Functions[0].FunctionName' 2>&1 | head -1
最低限必要な IAM マネージドポリシー: AmazonAPIGatewayAdministrator + AWSLambdaFullAccess + AmazonCognitoPowerUser。本番では最小権限原則でカスタムポリシーを使うこと。
2-2. 使用技術スタック
| AWS サービス | Terraform リソース | 役割 |
|---|---|---|
| API Gateway HTTP API | aws_apigatewayv2_api | HTTP API 本体 |
| API Gateway Authorizer | aws_apigatewayv2_authorizer | TOKEN/REQUEST/JWT 認証 |
| API Gateway Route | aws_apigatewayv2_route | ルーティング + Authorizer 紐付け |
| API Gateway Integration | aws_apigatewayv2_integration | Backend Lambda 統合 |
| API Gateway Stage | aws_apigatewayv2_stage | デプロイステージ ($default) |
| Lambda | aws_lambda_function | Authorizer ロジック + Backend |
| IAM Role | aws_iam_role | Lambda 実行ロール |
| IAM Policy | aws_iam_role_policy_attachment | Lambda 基本実行権限 |
| Cognito User Pool | aws_cognito_user_pool | JWT IdP (Cognito 選択時) |
| WAF v2 Web ACL | aws_wafv2_web_acl | 前段フィルタ (Regional) |
| CloudWatch Logs | aws_cloudwatch_log_group | アクセスログ + Authorizer ログ |
| X-Ray | tracing_config on Lambda | 分散トレーシング |
2-3. Terraform ワークスペース構成
terraform-apigateway-authorizer/
├── main.tf # プロバイダ設定 (aws ~> 5.0)
├── variables.tf# 変数定義
├── outputs.tf # 出力値 (api_endpoint / authorizer_id)
├── modules/
│├── api_gateway/ # HTTP API + Route + Stage
││├── main.tf
││├── variables.tf
││└── outputs.tf
│├── authorizer/# Lambda Authorizer 各種
││├── main.tf
││├── variables.tf
││└── outputs.tf
│└── lambda/ # Backend + Authorizer Lambda
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── envs/
├── dev/ # 開発環境
│├── main.tf
│└── terraform.tfvars
└── prod/# 本番環境
├── main.tf
└── terraform.tfvars
初期化と検証:
cd terraform-apigateway-authorizer/envs/dev
terraform init
terraform validate
terraform plan -out=tfplan
terraform apply tfplan
2-4. IAM 構成
本記事では 2 つの IAM ロールを使用します。
① api-gw-invoke-role (API Gateway → Lambda 呼び出し権限)
API Gateway が TOKEN/REQUEST Authorizer Lambda を呼び出すためのロールです (JWT Authorizer は Lambda 不要のため不要)。
resource "aws_iam_role" "api_gw_invoke" {
name = "api-gw-invoke-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "apigateway.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy" "api_gw_invoke_lambda" {
name = "invoke-authorizer-lambda"
role = aws_iam_role.api_gw_invoke.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect= "Allow"
Action= "lambda:InvokeFunction"
Resource = aws_lambda_function.authorizer.arn
}]
})
}
② authorizer-lambda-role (Lambda Authorizer 実行ロール)
Lambda Authorizer 関数の実行ロールです。X-Ray トレーシングを有効化するため AWSXRayDaemonWriteAccess も付与します。
resource "aws_iam_role" "authorizer_lambda" {
name = "authorizer-lambda-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "authorizer_basic" {
role = aws_iam_role.authorizer_lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy_attachment" "authorizer_xray" {
role = aws_iam_role.authorizer_lambda.name
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
}
2-5. 3点セット: Terraform + AWS CLI + コンソール
Terraform — HTTP API + Stage + CloudWatch Logs の最小構成
resource "aws_apigatewayv2_api" "this" {
name = "demo-http-api"
protocol_type = "HTTP"
description= "HTTP API + Lambda Authorizer デモ"
}
resource "aws_cloudwatch_log_group" "api_access" {
name = "/aws/apigateway/${aws_apigatewayv2_api.this.name}/access"
retention_in_days = 7
}
resource "aws_apigatewayv2_stage" "default" {
api_id= aws_apigatewayv2_api.this.id
name = "$default"
auto_deploy = true
access_log_settings {
destination_arn = aws_cloudwatch_log_group.api_access.arn
format = jsonencode({
requestId = "$context.requestId"
ip = "$context.identity.sourceIp"
requestTime = "$context.requestTime"
httpMethod= "$context.httpMethod"
routeKey = "$context.routeKey"
status = "$context.status"
authorizerError = "$context.authorizer.error"
})
}
}
AWS CLI — HTTP API と Authorizer の確認コマンド
# HTTP API 一覧取得
aws apigatewayv2 get-apis \
--query 'Items[?ProtocolType==`HTTP`].[ApiId,Name,CreatedDate]' \
--output table
# 特定 API の Authorizer 一覧
API_ID="YOUR_API_ID"
aws apigatewayv2 get-authorizers \
--api-id "${API_ID}" \
--query 'Items[*].[AuthorizerId,Name,AuthorizerType,IdentitySource]' \
--output table
# Route と Authorizer の紐付け確認
aws apigatewayv2 get-routes \
--api-id "${API_ID}" \
--query 'Items[*].[RouteId,RouteKey,AuthorizationType,AuthorizerId]' \
--output table
コンソール — Authorizer 設定確認手順
- AWS マネジメントコンソール → API Gateway を開く
- 左ペイン「APIs」→「HTTP API」タブで対象 API を選択
- 左メニュー「Authorization」→「Manage authorizers」タブ
- 作成済み Authorizer の一覧と詳細設定 (IdentitySource / TTL / AuthorizerPayloadFormatVersion) を確認
- 「Routes」タブで各ルートにどの Authorizer が紐付いているかを確認
3. REST API vs HTTP API 選択基準 (コスト・機能・レイテンシ比較)
- 本記事の構成: Client → WAF → HTTP API (apigatewayv2) → Authorizer → Backend Lambda
- コスト: HTTP API は 1M リクエスト $1.00 — REST API 比 約 71% 削減
- JWT Authorizer 内蔵: Cognito/Auth0 JWT 認証なら Lambda 不要のケースあり
- p99 レイテンシ: HTTP API は REST API より数 ms〜20 ms 低い (処理経路の短縮)
- REST API が必要なケース (API キー・リクエスト検証・エッジ最適化) は §3-2 判定フローで確認

3-1. 機能差分比較表
2020 年に GA となった HTTP API (apigatewayv2) は REST API (apigatewayv1) の後継に位置づけられますが、
2026 年現在も両世代が並存しています。以下の比較表で差分を確認し、移行可否を判断してください。
| 項目 | REST API (v1) | HTTP API (v2) | 備考 |
|---|---|---|---|
| 料金 (〜300M req/月) | $3.50 / 1M req | $1.00 / 1M req | HTTP API が 71% 安い |
| 料金 (300M〜1B req/月) | $1.51 / 1M req | $0.90 / 1M req | HTTP API が約 40% 安い |
| p99 レイテンシ | 高め | 低め | HTTP API は処理経路が短縮 |
| JWT Authorizer 内蔵 | ✗ Lambda 必須 | ✅ API GW が検証 | Cognito/Auth0 直結 |
| Lambda Authorizer | ✅ TOKEN/REQUEST | ✅ TOKEN/REQUEST | §4 で詳解 |
| API キー / 使用量プラン | ✅ | ✗ | 外部課金管理が必要なら REST |
| リクエスト Body 検証 | ✅ JSON Schema | ✗ | 検証を Lambda 側で実装が必要 |
| エッジ最適化エンドポイント | ✅ CloudFront 自動 | ✗ Regional only | CDN 前段が必要なら CloudFront 追加 |
| AWS X-Ray 統合 | ✅ セグメント詳細 | ✅ 一部制限あり | REST API が詳細 |
| カスタムドメイン | ✅ | ✅ | 同等 |
| $default ルート | ✗ | ✅ | フォールバック/プロキシ設計に有用 |
| マネージド CORS | ✗ 手動 Mock | ✅ API レベルで一括 | 設定工数削減 |
| スロットリング粒度 | ステージ/デプロイ単位 | ルート/ステージ単位 | HTTP API がきめ細か |
| VPC プライベートエンドポイント | ✅ | ✅ | 同等 |
コスト計算例 — 月間 1 億リクエスト
REST API: 100,000,000 req × $3.50 / 1,000,000 = $350.00/月
HTTP API: 100,000,000 req × $1.00 / 1,000,000 = $100.00/月
─────────
差額削減:$250.00/月 (71% 削減)
REST API で Lambda Authorizer を実装していた場合、HTTP API の JWT Authorizer 内蔵への置き換えで Lambda 実行コストと冷起動レイテンシをさらに削減できます。
3-2. HTTP API を選ぶべきケース / 選ぶべきでないケース
HTTP API を選ぶべきケース
以下をすべて満たす場合、HTTP API を採用または移行することを推奨します。
- Cognito User Pools か Auth0/Okta の JWT を認証基盤として使用する
- API キー・使用量プランによる外部サード向け従量課金管理が不要
- Body/Query の入力検証は Lambda 側で実施する (API Gateway 側検証不要)
- エッジ最適化エンドポイントが不要 (または CloudFront を別途配置する)
- 新規グリーンフィールドプロジェクト、または REST API 依存機能を使用していない
判定フロー (HTTP API か REST API か)
① API キー / 使用量プランが必要か?
YES → REST API を維持 (HTTP API は未対応)
NO → 次へ
② Body / Query の API Gateway レベル検証が必要か?
YES → REST API を維持
NO → 次へ
③ エッジ最適化エンドポイントが必要か?
YES → REST API を維持 (または CloudFront + HTTP API を検討)
NO → 次へ
④ AWS X-Ray セグメント詳細分析が必須か?
YES → REST API を検討 (HTTP API は Integration 単位のみ)
NO → HTTP API を採用
HTTP API を選ぶべきでないケース
| 要件 | 推奨 API | 理由 |
|---|---|---|
| API キー発行・使用量プラン | REST API | HTTP API は未対応 |
| リクエスト Body 検証 (JSON Schema) | REST API | HTTP API はサポートなし |
| エッジ最適化 (CloudFront 自動統合) | REST API | HTTP API は Regional のみ |
| X-Ray 詳細セグメントトレース | REST API | HTTP API は Integration 単位のみ |
| 既存 REST API (移行コスト高) | そのまま維持 | 段階移行を別記事で解説予定 |
移行の注意: REST API から HTTP API への移行はステージごとの段階切り替えを推奨します。
API キーを発行済みのクライアントがいる場合、HTTP API 側では API キー機能が使えないため
移行前に代替認証方式 (JWT Authorizer 等) への置き換えが必要です。
3-3. エンドポイント設計 ($default route / カスタムドメイン / CORS)
$default route の活用
HTTP API は $default という特殊なルートキーを持ちます。リクエストのパスとメソッドが
定義済みルートにマッチしない場合、$default が捕捉します。Lambda プロキシ統合と組み合わせることで
単一 Lambda 関数に全ルートを委譲するシンプルなプロキシアーキテクチャを実現できます。
ルーティング例:
GET /users→ users-lambda (専用ルート)
POST /users→ users-lambda (専用ルート)
GET /products/{id} → products-lambda (専用ルート)
$default→ fallback-lambda (404 カスタムレスポンス + ログ)
$default を設けない場合、未定義パスへのリクエストは {"message":"Not Found"} を返します。$default を設けることで 404 レスポンスのフォーマット統一と詳細ログ収集が可能になります。
カスタムドメインの設定
HTTP API はリージョナルエンドポイントのみのため、カスタムドメインに紐付ける ACM 証明書は
API Gateway が配置されたリージョンで発行してください (CloudFront 用の us-east-1 とは異なります)。
デフォルト URL : https://{api-id}.execute-api.{region}.amazonaws.com/{stage}
カスタム URL : https://api.example.com/v1
マネージド CORS の設定
HTTP API ではマネージド CORS をサポートし、REST API で必要だった Mock Integration による
OPTIONS ハンドラの手動設定が不要になります。
設定例:
allow_origins : ["https://app.example.com"]
allow_methods : ["GET", "POST", "PUT", "DELETE"]
allow_headers : ["Authorization", "Content-Type"]
max_age : 86400 (プリフライトキャッシュ: 24 時間)
注意: Lambda 関数内で
Access-Control-Allow-Originヘッダーを返している場合、
API Gateway のマネージド CORS と重複してヘッダーが 2 重付与されます。
API Gateway 側か Lambda 側のどちらか一方に統一してください (API Gateway 側を推奨)。
3-4. 3点セット — HTTP API 基本構成 (Terraform / AWS CLI / コンソール)
Terraform HCL
# HTTP API 本体
resource "aws_apigatewayv2_api" "main" {
name = "${var.app_name}-http-api"
protocol_type = "HTTP"
cors_configuration {
allow_origins = ["https://app.example.com"]
allow_methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
allow_headers = ["Authorization", "Content-Type"]
max_age = 86400
}
}
# prod ステージ (auto_deploy 有効)
resource "aws_apigatewayv2_stage" "prod" {
api_id= aws_apigatewayv2_api.main.id
name = "prod"
auto_deploy = true
access_log_settings {
destination_arn = aws_cloudwatch_log_group.api_gw.arn
format = jsonencode({
requestId = "$context.requestId"
sourceIp= "$context.identity.sourceIp"
requestTime= "$context.requestTime"
httpMethod = "$context.httpMethod"
routeKey= "$context.routeKey"
status = "$context.status"
responseLength= "$context.responseLength"
integrationError = "$context.integrationErrorMessage"
})
}
default_route_settings {
throttling_burst_limit = 5000
throttling_rate_limit = 10000
}
}
# CloudWatch ログ
resource "aws_cloudwatch_log_group" "api_gw" {
name = "/aws/apigateway/${var.app_name}"
retention_in_days = 30
}
# $default route (フォールバック)
resource "aws_apigatewayv2_route" "default" {
api_id = aws_apigatewayv2_api.main.id
route_key = "$default"
target = "integrations/${aws_apigatewayv2_integration.lambda_default.id}"
}
# Lambda プロキシ統合 ($default)
resource "aws_apigatewayv2_integration" "lambda_default" {
api_id = aws_apigatewayv2_api.main.id
integration_type = "AWS_PROXY"
integration_uri = aws_lambda_function.api_handler.invoke_arn
payload_format_version = "2.0"
}
output "api_endpoint" {
value = aws_apigatewayv2_stage.prod.invoke_url
}
AWS CLI
# HTTP API 作成
API_ID=$(aws apigatewayv2 create-api \
--name "my-http-api" \
--protocol-type "HTTP" \
--cors-configuration \
AllowOrigins="https://app.example.com",\
AllowMethods="GET POST PUT DELETE",\
AllowHeaders="Authorization Content-Type",\
MaxAge=86400 \
--query "ApiId" \
--output text)
echo "API ID: ${API_ID}"
# prod ステージ作成 (auto-deploy 有効)
aws apigatewayv2 create-stage \
--api-id "${API_ID}" \
--stage-name "prod" \
--auto-deploy \
--default-route-settings \
"ThrottlingBurstLimit=5000,ThrottlingRateLimit=10000"
# エンドポイント URL を確認
aws apigatewayv2 get-api \
--api-id "${API_ID}" \
--query "ApiEndpoint" \
--output text
コンソール手順
- AWS マネジメントコンソール → API Gateway → 「API を作成」をクリック
- API タイプで 「HTTP API」 を選択し「構築」をクリック
- 「統合を追加」でバックエンド Lambda 関数を指定 (後から追加も可能)
- API 名 (例:
my-http-api) を入力し「次へ」をクリック - ルートの追加画面でメソッド・パス・統合を設定し「次へ」をクリック
- ステージ名を
prod、「自動デプロイ」をオンにして「次へ」→「作成」 - 作成後、左メニュー「CORS」からオリジン・メソッド・ヘッダーを設定し「保存」
- 「デプロイ」をクリックし、画面上部のエンドポイント URL でアクセス確認
# デプロイ後ヘルスチェック
curl -sI "https://{api-id}.execute-api.{region}.amazonaws.com/prod/" \
| grep -E "HTTP|content-type|x-amzn"
4. Lambda Authorizer 3種完全比較 (TOKEN / REQUEST / JWT)

HTTP API における認証認可の核心は Authorizer 種別の選択設計にある。TOKEN・REQUEST・JWT の3種はユースケースと制約が異なり、誤選択はパフォーマンス劣化・セキュリティリスク・コスト増大に直結する。
4-1. Authorizer 3種 概要と選択フロー
| 項目 | TOKEN Authorizer | REQUEST Authorizer | JWT Authorizer |
|---|---|---|---|
| 対応 API | REST / HTTP API | REST / HTTP API | HTTP API 専用 |
| identitySource | Authorization ヘッダー 1個 | ヘッダー/クエリ/パス/コンテキスト | Authorization ヘッダー (JWT) |
| Lambda 実行 | 毎リクエスト (キャッシュ可) | 毎リクエスト (キャッシュ可) | 不要 (APIGW 直接検証) |
| キャッシュ | authorizerResultTtlInSeconds | authorizerResultTtlInSeconds | N/A |
| 主なユースケース | 独自 Bearer トークン検証 | IP + ヘッダー + クエリ複合判定 | OIDC/OAuth2 標準 JWT 検証 |
| コスト | Lambda 実行費 + APIGW | Lambda 実行費 + APIGW | APIGW のみ (最安) |
| 実装複雑度 | 低〜中 | 中〜高 | 低 (設定のみ) |
選択フロー:
- JWT を OIDC/OAuth2 標準仕様で検証するだけでよい → JWT Authorizer (Lambda 不要・最安・最速)
- Cognito User Pools / Auth0 / Okta が IdP なら第一候補
- カスタム検証ロジックが必要で
Authorizationヘッダーのみで判定できる → TOKEN Authorizer - IP アドレス・複数ヘッダー・クエリパラメータの組み合わせで判定 → REQUEST Authorizer
4-2. QG-2: Authorizer 3種リクエストフロー比較
◆ TOKEN Authorizer
Client ──[Authorization: Bearer <token>]──▶ API Gateway (HTTP API)
identitySource: $request.header.Authorization
キャッシュ (TTL 0-3600秒) ヒット → Lambda スキップして Allow/Deny を再利用
キャッシュミス → Lambda Authorizer 呼び出し
event.authorizationToken = "Bearer eyJhbGc..."
event.methodArn = "arn:aws:execute-api:ap-northeast-1:123456789012:abc123/prod/GET/items"
↓
IAM Policy Document 返却
{ "Effect": "Allow" } → バックエンド Lambda 呼び出し
{ "Effect": "Deny" } → 403 Forbidden
raise Exception("Unauthorized") → 401 Unauthorized
◆ REQUEST Authorizer
Client ──[x-api-key + x-tenant-id ヘッダー + クエリ]──▶ API Gateway (HTTP API)
identitySource: $request.header.x-api-key,$request.header.x-tenant-id
キャッシュキー = identitySource の値の組み合わせ
→ Lambda Authorizer 呼び出し
event.headers= { "x-api-key": "prod-key-abc", "x-tenant-id": "tenant-001" }
event.queryStringParameters = { "version": "v2" }
event.requestContext.http.sourceIp = "203.0.113.42"
↓
複合条件判定: IP 許可リスト AND APIキー検証 AND テナント検証
→ IAM Policy Document 返却 → バックエンド or 403
◆ JWT Authorizer
Client ──[Authorization: Bearer <JWT>]──▶ API Gateway (HTTP API)
identitySource: $request.header.Authorization
Lambda 呼び出しなし — API Gateway が直接 JWT 検証
1. JWKS エンドポイントから公開鍵取得 (内部キャッシュ)
2. issuer 完全一致確認
Cognito: https://cognito-idp.{region}.amazonaws.com/{UserPoolId}/ ← 末尾スラッシュ必須
3. audience 一致確認
4. 署名・有効期限検証
↓
検証成功 → バックエンド Lambda ($context.authorizer.jwt.claims でクレーム参照)
検証失敗 → 401 Unauthorized
4-3. TOKEN Authorizer 実装 (Python Lambda handler)
TOKEN Authorizer は event.authorizationToken に Bearer トークンが届く。検証後に IAM Policy Document を返却する。
import re
def handler(event, context):
token = event.get("authorizationToken", "")
method_arn = event["methodArn"]
match = re.match(r"^Bearer\s+(.+)$", token, re.IGNORECASE)
if not match:
raise Exception("Unauthorized") # → 401
jwt_token = match.group(1)
principal_id = verify_token(jwt_token) # 独自トークン検証ロジック
effect = "Allow" if principal_id else "Deny"
return _build_policy(principal_id or "anonymous", effect, method_arn)
def _build_policy(principal_id: str, effect: str, method_arn: str) -> dict:
# Resource をワイルドカード化すると同 API の全 Route にキャッシュが適用される
parts = method_arn.split(":")
api_id_stage = parts[5].split("/")[0]
resource = f"arn:aws:execute-api:{parts[3]}:{parts[4]}:{api_id_stage}/*/*"
return {
"principalId": principal_id,
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{"Action": "execute-api:Invoke", "Effect": effect, "Resource": resource}
],
},
"context": {"userId": principal_id},
}
raise Exception("Unauthorized")→ 401 /Effect: DenyPolicy → 403 (挙動が異なる)contextに格納した値は$context.authorizer.userIdでバックエンドから参照可能- Resource に
/*/*を使うとキャッシュ効率が向上するが、Route 個別の Deny 制御は失われる
4-4. REQUEST Authorizer 実装 (複合条件判定)
REQUEST Authorizer は event.headers・event.queryStringParameters・event.requestContext を横断参照できるため、IP 制限 + テナント検証 + API キー検証を1 Lambda で実装できる。
import ipaddress
ALLOWED_NETWORKS = [
ipaddress.ip_network("203.0.113.0/24"),
ipaddress.ip_network("198.51.100.0/24"),
]
VALID_API_KEYS = {"prod-key-abc123", "prod-key-def456"}
def handler(event, context):
headers = event.get("headers", {})
source_ip = event.get("requestContext", {}).get("http", {}).get("sourceIp", "")
method_arn = event["routeArn"]
try:
ip_ok = any(ipaddress.ip_address(source_ip) in net for net in ALLOWED_NETWORKS)
except ValueError:
ip_ok = False
api_key = headers.get("x-api-key", "")
tenant_id = headers.get("x-tenant-id", "")
effect = "Allow" if (ip_ok and api_key in VALID_API_KEYS and bool(tenant_id)) else "Deny"
return {
"principalId": tenant_id or "anonymous",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{"Action": "execute-api:Invoke", "Effect": effect, "Resource": method_arn}
],
},
"context": {"tenantId": tenant_id, "ipAllowed": str(ip_ok)},
}
identitySource 設計の注意点: identitySource の値の組み合わせがキャッシュキーになる。IP アドレス ($context.identity.sourceIp) をキャッシュキーに含めると IP が変わるたびにキャッシュミスが発生するため、認証判定に必須な要素のみ に絞ること。
4-5. JWT Authorizer 設定と落とし穴
JWT Authorizer は Lambda 実行不要で API Gateway が直接 JWT 署名検証を行う。Cognito と Auth0 で設定差分がある。
| パラメータ | Cognito User Pools | Auth0 |
|---|---|---|
| issuer | https://cognito-idp.{region}.amazonaws.com/{UserPoolId}/ | https://{tenant}.auth0.com/ |
| audience | ["{AppClientId}"] | ["https://api.example.com"] |
| identitySource | $request.header.Authorization | $request.header.Authorization |
- Cognito が発行する JWT の
issクレームは末尾スラッシュ あり:https://cognito-idp.ap-northeast-1.amazonaws.com/{UserPoolId}/ - API Gateway の
issuer設定と JWT のissクレームが 完全一致しないと 401 Unauthorized になる - Auth0 も末尾スラッシュ必須。Okta は設定によりスラッシュなしの場合もある
- 診断:
python3 -c "import base64,json,sys; t=sys.argv[1]; print(json.loads(base64.b64decode(t.split('.')[1]+'==')))" <JWT>でissを確認
バックエンド Lambda から JWT クレームを参照する方法:
def handler(event, context):
# JWT Authorizer 検証後、クレームは requestContext.authorizer.jwt.claims に格納
claims = event["requestContext"]["authorizer"]["jwt"]["claims"]
user_sub = claims.get("sub", "")
email = claims.get("email", "")
groups = claims.get("cognito:groups", "")
return {"statusCode": 200, "body": f"user={email}, sub={user_sub}"}
4-6. 3点セット: Terraform / AWS CLI / コンソール
Terraform HCL (aws_apigatewayv2_authorizer 3種)
# TOKEN Authorizer (Authorization ヘッダー 1個)
resource "aws_apigatewayv2_authorizer" "token" {
api_id = aws_apigatewayv2_api.main.id
authorizer_type = "REQUEST"
identity_sources = ["$request.header.Authorization"]
name = "token-authorizer"
authorizer_uri = aws_lambda_function.token_authorizer.invoke_arn
authorizer_payload_format_version = "2.0"
authorizer_result_ttl_in_seconds = 300
enable_simple_responses = false
}
# REQUEST Authorizer (複数 identitySource でキャッシュキーを構成)
resource "aws_apigatewayv2_authorizer" "request" {
api_id = aws_apigatewayv2_api.main.id
authorizer_type = "REQUEST"
identity_sources = [
"$request.header.x-api-key",
"$request.header.x-tenant-id",
]
name = "request-authorizer"
authorizer_uri = aws_lambda_function.request_authorizer.invoke_arn
authorizer_payload_format_version = "2.0"
authorizer_result_ttl_in_seconds = 300
enable_simple_responses = false
}
# JWT Authorizer (Cognito User Pools — Lambda 不要)
resource "aws_apigatewayv2_authorizer" "jwt_cognito" {
api_id = aws_apigatewayv2_api.main.id
authorizer_type = "JWT"
identity_sources = ["$request.header.Authorization"]
name = "jwt-cognito-authorizer"
jwt_configuration {
# issuer 末尾スラッシュ必須 (Cognito の iss クレームに合わせる)
issuer= "https://cognito-idp.${var.region}.amazonaws.com/${aws_cognito_user_pool.main.id}/"
audience = [aws_cognito_user_pool_client.main.id]
}
}
# Lambda permission (TOKEN Authorizer)
resource "aws_lambda_permission" "token_authorizer" {
statement_id = "AllowAPIGatewayInvokeTokenAuth"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.token_authorizer.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.main.execution_arn}/*"
}
# Lambda permission (REQUEST Authorizer)
resource "aws_lambda_permission" "request_authorizer" {
statement_id = "AllowAPIGatewayInvokeRequestAuth"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.request_authorizer.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.main.execution_arn}/*"
}
# Route に JWT Authorizer を紐付け
resource "aws_apigatewayv2_route" "jwt_protected" {
api_id = aws_apigatewayv2_api.main.id
route_key = "GET /secure"
authorization_type = "JWT"
authorizer_id= aws_apigatewayv2_authorizer.jwt_cognito.id
}
# Route に TOKEN Authorizer を紐付け
resource "aws_apigatewayv2_route" "token_protected" {
api_id = aws_apigatewayv2_api.main.id
route_key = "GET /protected"
authorization_type = "CUSTOM"
authorizer_id= aws_apigatewayv2_authorizer.token.id
}
AWS CLI
# TOKEN / REQUEST Authorizer 作成 (authorizer-type は REQUEST で統一)
aws apigatewayv2 create-authorizer \
--api-id "${API_ID}" \
--authorizer-type REQUEST \
--identity-source '$request.header.Authorization' \
--name token-authorizer \
--authorizer-uri "arn:aws:apigateway:${REGION}:lambda:path/2015-03-31/functions/${LAMBDA_ARN}/invocations" \
--authorizer-payload-format-version "2.0" \
--authorizer-result-ttl-in-seconds 300
# JWT Authorizer 作成 (Cognito — issuer 末尾スラッシュに注意)
aws apigatewayv2 create-authorizer \
--api-id "${API_ID}" \
--authorizer-type JWT \
--identity-source '$request.header.Authorization' \
--name jwt-cognito-authorizer \
--jwt-configuration \
Issuer="https://cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}/",Audience="${CLIENT_ID}"
# 作成済み Authorizer 一覧
aws apigatewayv2 get-authorizers --api-id "${API_ID}" --output table
# Route に Authorizer を紐付け
aws apigatewayv2 update-route \
--api-id "${API_ID}" \
--route-id "${ROUTE_ID}" \
--authorization-type JWT \
--authorizer-id "${AUTHORIZER_ID}"
コンソール操作
- API Gateway コンソール → 対象の HTTP API を選択
- 左ペイン「Authorization」→「Manage authorizers」タブ → 「Create」
- Authorizer type を選択:
Lambda(TOKEN/REQUEST) またはJWT Lambda選択時: Lambda 関数 ARN・Payload format version2.0・TTL・Identity sources を入力JWT選択時: Issuer URL (末尾スラッシュ必須)・Audience・Identity source を入力- 作成後: 左ペイン「Routes」→ 対象 Route 選択 →「Attach authorizer」で紐付け
# 設定値の確認 (issuer の末尾スラッシュを確認する際に有用)
aws apigatewayv2 get-authorizer \
--api-id "${API_ID}" \
--authorizer-id "${AUTHORIZER_ID}" \
--query '[Name,AuthorizerType,IdentitySources,JwtConfiguration]'
5. Cognito Authorizer vs Lambda Authorizer 使い分け + サードパーティIdP統合
5-1. Cognito User Pools vs Lambda Authorizer 選択指針
HTTP API の認証認可には 3 つのアプローチが存在し、それぞれ適用条件が異なる。
Cognito User Pools 内蔵 JWT Authorizer (REST API) は API Gateway が Cognito User Pools の JWKS を自動取得して JWT 署名を検証する。Lambda 実行コストが発生しない一方、認可ロジックは JWT クレームのみに限定される。REST API 専用機能。
HTTP API 内蔵 JWT Authorizer は HTTP API 専用で、issuer と audience を指定するだけで JWT 検証を API Gateway 側で処理する。Cognito だけでなく Auth0、Okta、任意の OIDC 準拠 IdP に対応する最もシンプルな選択肢。Lambda 不要。
Lambda Authorizer (REQUEST) は最大の柔軟性を持つ。外部 DB 参照や複数 IdP 並列対応、HMAC / API Key 検証など、JWT 以外の認証方式も処理できる。Caching (TTL 0-3600 秒) による Lambda 実行回数の最適化が重要。
| 特性 | REST API Cognito Authorizer | HTTP API JWT Authorizer | Lambda Authorizer |
|---|---|---|---|
| Lambda 実行 | なし | なし | あり |
| 対応 IdP | Cognito のみ | Cognito/Auth0/Okta/任意 OIDC | 任意 |
| API 種別 | REST API のみ | HTTP API のみ | REST/HTTP 両対応 |
| カスタムロジック | 不可 | 不可 | 可 (外部 DB/複数 IdP) |
| Caching | なし | なし | あり (TTL 0-3600s) |
| 追加コスト | なし | なし | Lambda 実行分 |
| JWKs 自動取得 | Cognito 統合のみ | jwks_uri 指定 | 自実装 |
| 推奨シナリオ | REST API + Cognito | HTTP API + OIDC | 複雑な認可/API Key |
選択フロー:
1. HTTP API を採用しているか?→ No なら REST API + Cognito Authorizer または Lambda Authorizer
2. IdP が OIDC 準拠 (Cognito / Auth0 / Okta) か?→ Yes かつカスタムロジック不要 → HTTP API JWT Authorizer
3. 複数 IdP / 外部 DB 参照 / API Key 認証が必要 → Lambda Authorizer (REQUEST)
5-2. QG-3: Cognito vs Auth0/Okta 統合パターン
HTTP API 内蔵 JWT Authorizer を使う場合、IdP によって issuer と jwks_uri の形式が異なる。誤設定は全エンドポイントで 401 が返り続ける障害に直結する。
| IdP | issuer (JWT Authorizer 設定値) | jwks_uri (API GW が自動導出) | audience |
|---|---|---|---|
| Cognito User Pools | https://cognito-idp.{region}.amazonaws.com/{UserPoolId} (末尾スラッシュなし) | issuer + /.well-known/jwks.json | App Client ID |
| Auth0 | https://{tenant}.auth0.com/ (末尾スラッシュ必須) | issuer + .well-known/jwks.json | API Identifier (例: https://api.example.com) |
| Okta | https://{domain}/oauth2/{authServerId} (Default の場合 /oauth2/default) | issuer + /v1/keys | API audience URI |
- Cognito: issuer に末尾スラッシュを付けない。accessToken または idToken を Bearer として送信。
- Auth0: issuer に末尾スラッシュが必須。Custom Domain 使用時も末尾スラッシュ必須。API Identifier は https://… 形式の任意 URI。
- Okta: デフォルト Authorization Server は /oauth2/default。Custom Authorization Server は /oauth2/{id}。issuer と jwks_uri の対応関係は Authorization Server ごとに異なる。
採用判断軸: 国内エンタープライズは Cognito 主軸 (IAM 統合・コスト最適)。SaaS/B2B 向け API は Auth0 (多テナント対応・Social Login・MFA 標準)。Okta は既存 Okta IdP が社内標準化されている場合に採用。

5-3. Cognito User Pools + HTTP API JWT Authorizer 統合実装
HTTP API の JWT Authorizer で Cognito を使う場合、jwt_configuration の issuer に Cognito User Pools の OIDC Discovery ベース URL (末尾スラッシュなし)、audience に App Client ID を指定する。
Cognito User Pool と App Client の作成:
resource "aws_cognito_user_pool" "main" {
name = "${var.project}-user-pool"
password_policy {
minimum_length = 12
require_lowercase = true
require_numbers= true
require_symbols= true
require_uppercase = true
}
auto_verified_attributes = ["email"]
}
resource "aws_cognito_user_pool_client" "api" {
name= "${var.project}-api-client"
user_pool_id = aws_cognito_user_pool.main.id
allowed_oauth_flows= ["code"]
allowed_oauth_scopes = ["openid", "email", "profile"]
allowed_oauth_flows_user_pool_client = true
callback_urls= ["https://${var.domain}/callback"]
access_token_validity = 60 # 分
refresh_token_validity = 30 # 日
id_token_validity= 60 # 分
token_validity_units {
access_token = "minutes"
refresh_token = "days"
id_token= "minutes"
}
enable_token_revocation = true
}
HTTP API + Cognito JWT Authorizer:
resource "aws_apigatewayv2_authorizer" "cognito_jwt" {
api_id = aws_apigatewayv2_api.main.id
authorizer_type = "JWT"
identity_sources = ["$request.header.Authorization"]
name = "${var.project}-cognito-jwt"
jwt_configuration {
audience = [aws_cognito_user_pool_client.api.id]
# 末尾スラッシュなし
issuer = "https://cognito-idp.${var.aws_region}.amazonaws.com/${aws_cognito_user_pool.main.id}"
}
}
AWS CLI 手順:
HTTP_API_ID=$(aws apigatewayv2 create-api \
--name my-http-api \
--protocol-type HTTP \
--query 'ApiId' --output text)
aws apigatewayv2 create-authorizer \
--api-id "$HTTP_API_ID" \
--name cognito-jwt \
--authorizer-type JWT \
--identity-source '$request.header.Authorization' \
--jwt-configuration \
"Audience=[\"your-app-client-id\"],Issuer=https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXX"
コンソール操作:
1. API Gateway → HTTP APIs → 対象 API → 左メニュー「Authorization」→「Manage authorizers」→「Create」
2. Authorizer type: JWT / Identity source: $request.header.Authorization
3. Issuer URL: https://cognito-idp.ap-northeast-1.amazonaws.com/{UserPoolId} (末尾スラッシュなし)
4. Audience: App Client ID を入力 → Save
5-4. Auth0 統合実装 (JWKs endpoint + audience 設定)
Auth0 の場合、issuer は Auth0 テナント URL (末尾スラッシュ必須)、audience は Auth0 Dashboard で作成した API の Identifier。
resource "aws_apigatewayv2_authorizer" "auth0_jwt" {
api_id = aws_apigatewayv2_api.main.id
authorizer_type = "JWT"
identity_sources = ["$request.header.Authorization"]
name = "${var.project}-auth0-jwt"
jwt_configuration {
audience = [var.auth0_api_identifier]
# 末尾スラッシュ必須 (なしだと 401 が返り続ける)
issuer = "https://${var.auth0_domain}/"
}
}
Auth0 Dashboard での設定 (Applications → APIs → Create API):
1. Name: My AWS HTTP API、Identifier: https://api.example.com、Algorithm: RS256
2. JWT Authorizer の issuer に https://{tenant}.auth0.com/ (末尾スラッシュ付き)
3. audience に Auth0 API Identifier を設定
JWKS 確認コマンド:
curl -s "https://your-tenant.auth0.com/.well-known/jwks.json" | jq '.keys[0].kid'
5-5. Okta 補足言及
Okta の JWT Authorizer は Auth0 と基本構造が同じ (OIDC 準拠) だが、issuer の形式が Authorization Server 種別によって異なる。
| Authorization Server 種別 | issuer 形式 |
|---|---|
| Default | https://{domain}/oauth2/default |
| Custom (named) | https://{domain}/oauth2/{authServerId} |
| Org (非推奨) | https://{domain} |
resource "aws_apigatewayv2_authorizer" "okta_jwt" {
api_id = aws_apigatewayv2_api.main.id
authorizer_type = "JWT"
identity_sources = ["$request.header.Authorization"]
name = "${var.project}-okta-jwt"
jwt_configuration {
audience = [var.okta_audience]
issuer= "https://${var.okta_domain}/oauth2/${var.okta_auth_server_id}"
}
}
Okta の JWKS URI は {issuer}/v1/keys (Auth0 の .well-known/jwks.json とパスが異なる)。HTTP API の JWT Authorizer は OIDC Discovery ドキュメント ({issuer}/.well-known/openid-configuration) を参照して JWKS URI を自動導出するため、Okta の Custom Authorization Server が openid-configuration を正しく公開しているか事前確認する。
5-6. 3点セット まとめ
| 操作 | Terraform | AWS CLI | コンソール |
|---|---|---|---|
| JWT Authorizer 作成 | aws_apigatewayv2_authorizer (type=JWT) | create-authorizer --authorizer-type JWT | Authorization → Create (JWT) |
| issuer 設定 | jwt_configuration.issuer | --jwt-configuration Issuer=... | Issuer URL フィールド |
| audience 設定 | jwt_configuration.audience | --jwt-configuration Audience=[...] | Audience フィールド |
| Authorizer 確認 | terraform show | get-authorizer --api-id $ID --authorizer-id $AID | Manage authorizers |
| 動作テスト | terraform apply 後 curl -H "Authorization: Bearer $TOKEN" $ENDPOINT | 同左 | Test 機能なし (curl 推奨) |
6. Terraform 実装 (aws_apigatewayv2_authorizer + aws_lambda_function 完全 HCL)
6-1. ディレクトリ構成 (modules/ + envs/)
本番実装では modules/ + envs/ の分離構成を採用し、HTTP API の再利用性を確保する。
terraform/
├── modules/
│├── http-api/
││├── main.tf # aws_apigatewayv2_api + stage + authorizer + route
││├── lambda.tf # aws_lambda_function (authorizer) + aws_lambda_permission
││├── iam.tf # aws_iam_role + aws_iam_role_policy
││├── variables.tf
││└── outputs.tf
│└── cognito/
│ ├── main.tf # aws_cognito_user_pool + aws_cognito_user_pool_client
│ ├── variables.tf
│ └── outputs.tf
└── envs/
├── dev/
│├── main.tf
│├── terraform.tfvars
│└── backend.tf
└── prod/
├── main.tf
├── terraform.tfvars
└── backend.tf
6-2. aws_apigatewayv2_api + aws_apigatewayv2_stage
# modules/http-api/main.tf
resource "aws_apigatewayv2_api" "main" {
name = "${var.project}-http-api"
protocol_type = "HTTP"
description= "HTTP API with Lambda Authorizer"
cors_configuration {
allow_headers = ["Content-Type", "Authorization", "X-Api-Key"]
allow_methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
allow_origins = var.cors_allow_origins
max_age = 300
}
}
resource "aws_apigatewayv2_stage" "default" {
api_id= aws_apigatewayv2_api.main.id
name = "$default"
auto_deploy = true
access_log_settings {
destination_arn = aws_cloudwatch_log_group.api_access.arn
format = jsonencode({
requestId = "$context.requestId", sourceIp = "$context.identity.sourceIp"
routeKey = "$context.routeKey", status = "$context.status"
authorizerError = "$context.authorizer.error"
})
}
default_route_settings {
throttling_burst_limit= 100
throttling_rate_limit = 50
detailed_metrics_enabled = true
}
}
resource "aws_cloudwatch_log_group" "api_access" {
name = "/aws/apigateway/${var.project}-http-api"
retention_in_days = 30
}
6-3. aws_apigatewayv2_authorizer 3種フル実装 (TOKEN/REQUEST/JWT)
HTTP API では LAMBDA (TOKEN/REQUEST 相当) と JWT の 2 系統が使用可能。REST API の TOKEN タイプは HTTP API に存在しないため、単一ヘッダーのみ検証する場合も REQUEST タイプで identity_sources を 1 つに絞る。
TOKEN 相当 (単一ヘッダー: Authorization のみ):
resource "aws_apigatewayv2_authorizer" "token_style" {
api_id = aws_apigatewayv2_api.main.id
authorizer_type = "REQUEST"
authorizer_uri = aws_lambda_function.token_authorizer.invoke_arn
identity_sources= ["$request.header.Authorization"]
name= "${var.project}-token-authorizer"
authorizer_payload_format_version = "2.0"
enable_simple_responses = true
# Authorization ヘッダー値をキャッシュキーとして TTL=300s
authorizer_result_ttl_in_seconds = 300
}
REQUEST Authorizer (複数ソース: ヘッダー + クエリ):
resource "aws_apigatewayv2_authorizer" "request" {
api_id = aws_apigatewayv2_api.main.id
authorizer_type = "REQUEST"
authorizer_uri = aws_lambda_function.request_authorizer.invoke_arn
identity_sources = [
"$request.header.Authorization",
"$request.header.X-Api-Key",
"$request.querystring.tenant_id",
]
name= "${var.project}-request-authorizer"
authorizer_payload_format_version = "2.0"
enable_simple_responses = false
# identity_sources の組み合わせがキャッシュキー
authorizer_result_ttl_in_seconds = 300
}
JWT Authorizer (HTTP API 専用: Cognito/Auth0/Okta):
resource "aws_apigatewayv2_authorizer" "jwt" {
api_id = aws_apigatewayv2_api.main.id
authorizer_type = "JWT"
identity_sources = ["$request.header.Authorization"]
name = "${var.project}-jwt-authorizer"
jwt_configuration {
audience = [var.jwt_audience]
issuer= var.jwt_issuer
}
}
6-4. aws_apigatewayv2_route + aws_apigatewayv2_integration
# Backend Lambda Integration
resource "aws_apigatewayv2_integration" "backend" {
api_id = aws_apigatewayv2_api.main.id
integration_type = "AWS_PROXY"
integration_uri = var.backend_lambda_invoke_arn
payload_format_version = "2.0"
timeout_milliseconds= 29000
}
# JWT Authorizer 使用ルート (認証必須)
resource "aws_apigatewayv2_route" "protected_jwt" {
api_id = aws_apigatewayv2_api.main.id
route_key = "GET /api/data"
target = "integrations/${aws_apigatewayv2_integration.backend.id}"
authorization_type = "JWT"
authorizer_id= aws_apigatewayv2_authorizer.jwt.id
}
# Lambda Authorizer 使用ルート (複雑な認可ロジック)
resource "aws_apigatewayv2_route" "protected_lambda" {
api_id = aws_apigatewayv2_api.main.id
route_key = "POST /api/admin"
target = "integrations/${aws_apigatewayv2_integration.backend.id}"
authorization_type = "CUSTOM"
authorizer_id= aws_apigatewayv2_authorizer.request.id
}
# 認証不要ルート (ヘルスチェック等)
resource "aws_apigatewayv2_route" "public" {
api_id = aws_apigatewayv2_api.main.id
route_key = "GET /health"
target = "integrations/${aws_apigatewayv2_integration.backend.id}"
authorization_type = "NONE"
}
# Backend Lambda 呼び出し権限
resource "aws_lambda_permission" "backend" {
statement_id = "AllowAPIGatewayInvokeBackend"
action = "lambda:InvokeFunction"
function_name = var.backend_lambda_function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.main.execution_arn}/*/*"
}
6-5. aws_lambda_function (Authorizer Lambda) + aws_iam_role
Authorizer Lambda 実装 (Python 3.12 / format 2.0):
# modules/http-api/authorizer/index.py
def handler(event, context):
"""HTTP API format 2.0 REQUEST Authorizer (simple responses モード)"""
headers = event.get("headers", {})
token = headers.get("authorization", "")
api_key = headers.get("x-api-key", "")
if not token or not api_key:
return {"isAuthorized": False}
# 本番: boto3 で DynamoDB/Secrets Manager を参照して検証
return {
"isAuthorized": True,
"context": {"userId": token.split(".")[-1], "tier": "premium"},
}
Authorizer Lambda の Terraform HCL:
# modules/http-api/lambda.tf
data "archive_file" "authorizer" {
type = "zip"
source_dir = "${path.module}/authorizer"
output_path = "${path.module}/.dist/authorizer.zip"
}
resource "aws_lambda_function" "request_authorizer" {
function_name = "${var.project}-request-authorizer"
filename= data.archive_file.authorizer.output_path
source_code_hash = data.archive_file.authorizer.output_base64sha256
handler = "index.handler"
runtime = "python3.12"
role = aws_iam_role.authorizer.arn
environment {
variables = {
LOG_LEVEL = var.log_level
}
}
tracing_config {
mode = "Active"
}
}
resource "aws_lambda_permission" "authorizer" {
statement_id = "AllowAPIGatewayInvokeAuthorizer"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.request_authorizer.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.main.execution_arn}/*/*"
}
IAM Role + Policy:
# modules/http-api/iam.tf
resource "aws_iam_role" "authorizer" {
name = "${var.project}-authorizer-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "authorizer_basic" {
role = aws_iam_role.authorizer.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy_attachment" "authorizer_xray" {
role = aws_iam_role.authorizer.name
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
}
resource "aws_iam_role_policy" "authorizer_custom" {
name = "${var.project}-authorizer-policy"
role = aws_iam_role.authorizer.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect= "Allow"
Action= ["dynamodb:GetItem", "dynamodb:Query"]
Resource = var.auth_table_arn
},
{
Effect= "Allow"
Action= ["secretsmanager:GetSecretValue"]
Resource = var.jwt_secret_arn
},
]
})
}
6-6. outputs.tf + variables.tf
# modules/http-api/variables.tf (主要変数抜粋)
variable "project"{ type = string }
variable "aws_region"{ type = string; default = "ap-northeast-1" }
variable "cors_allow_origins" { type = list(string); default = ["*"] }
variable "jwt_audience" { type = string; description = "Cognito App Client ID or Auth0 API Identifier" }
variable "jwt_issuer"{ type = string; description = "Cognito: 末尾スラッシュなし / Auth0: 末尾スラッシュあり" }
variable "backend_lambda_invoke_arn"{ type = string }
variable "backend_lambda_function_name" { type = string }
variable "auth_table_arn" { type = string; default = "" }
variable "jwt_secret_arn" { type = string; default = "" }
variable "log_level" { type = string; default = "INFO" }
# modules/http-api/outputs.tf
output "api_endpoint" { value = aws_apigatewayv2_api.main.api_endpoint }
output "api_id" { value = aws_apigatewayv2_api.main.id }
output "jwt_authorizer_id" { value = aws_apigatewayv2_authorizer.jwt.id }
output "request_authorizer_id" { value = aws_apigatewayv2_authorizer.request.id }
output "authorizer_lambda_arn" { value = aws_lambda_function.request_authorizer.arn }
output "execution_arn"{ value = aws_apigatewayv2_api.main.execution_arn }
6-7. 落とし穴 HCL 例 (よくある設定ミス)
- HTTP API で authorizer_type = “TOKEN” を指定する
TOKEN タイプは REST API 専用。HTTP API ではauthorizer_type = "REQUEST"または"JWT"のみ有効。
エラー例:ValidationException: Invalid authorizer type TOKEN for HTTP API - authorizer_payload_format_version の省略
HTTP API の Lambda Authorizer では必須フィールド。省略すると 1.0 フォーマット (REST API 互換) が適用され、イベント構造が変わり Lambda が正常動作しない。HTTP API では"2.0"を必ず指定。 - enable_simple_responses = true なのに IAM Policy Document を返す
enable_simple_responses = trueの場合、Lambda は{"isAuthorized": true}形式のみ受け付ける。{"principalId": ..., "policyDocument": {...}}を返すと 500 Internal Server Error になる。 - JWT issuer の末尾スラッシュを統一しない
Cognito: 末尾スラッシュなし、Auth0: 末尾スラッシュあり。逆に設定すると全エンドポイントで 401 が返り続ける。
確認コマンド:aws apigatewayv2 get-authorizer --api-id $ID --authorizer-id $AID --query 'JwtConfiguration.Issuer'
7. Caching 戦略 + WAF 統合 + 観測性

7-1. Authorizer Result キャッシュ設計 (authorizerResultTtlInSeconds)
Lambda Authorizer はリクエストごとに Lambda を起動するためコストとレイテンシが発生する。authorizerResultTtlInSeconds を設定することで、API Gateway がキャッシュした IAM ポリシーを再利用し、コストと p99 レイテンシを大幅に削減できる。
| TTL 設定値 | 動作 | 月間コスト試算 (1,000 req/min 想定) | 推奨用途 |
|---|---|---|---|
0 | キャッシュ無効 — 全リクエストで Lambda 起動 | Lambda 呼出 43,200,000 回/月 | セッショントークン・短命 JWT |
300 (5分) | 5分間キャッシュ — Lambda 呼出を大幅削減 | Lambda 呼出 約 144,000 回/月 (99.7% 削減) | 一般的なアクセストークン |
3600 (1時間) | 1時間キャッシュ — 最大節約 | Lambda 呼出 約 24,000 回/月 (99.9% 削減) | 長寿命サービスアカウントキー |
キャッシュ有効時のリクエストフロー:
Client → API Gateway → [キャッシュ Hit?]
├─ Yes: キャッシュ済みポリシー使用 → Lambda Authorizer スキップ → Backend
└─ No: Lambda Authorizer 起動 → ポリシー評価 → キャッシュ保存 → Backend
# JWT Authorizer (TTL は JWT 有効期限の半分以下を推奨)
resource "aws_apigatewayv2_authorizer" "jwt_cached" {
api_id = aws_apigatewayv2_api.http_api.id
authorizer_type = "JWT"
identity_sources = ["$request.header.Authorization"]
name = "jwt-authorizer"
jwt_configuration {
audience = [var.jwt_audience]
issuer= var.jwt_issuer
}
}
# Lambda REQUEST Authorizer (TTL あり)
resource "aws_apigatewayv2_authorizer" "lambda_cached" {
api_id = aws_apigatewayv2_api.http_api.id
authorizer_type = "REQUEST"
authorizer_uri = aws_lambda_function.authorizer.invoke_arn
identity_sources= ["$request.header.Authorization"]
name= "lambda-authorizer"
authorizer_result_ttl_in_seconds = 300
authorizer_payload_format_version = "2.0"
}
# AWS CLI で TTL を確認
aws apigatewayv2 get-authorizer \
--api-id $API_ID \
--authorizer-id $AUTHORIZER_ID \
--query "AuthorizerResultTtlInSeconds"
# AWS CLI で TTL を更新
aws apigatewayv2 update-authorizer \
--api-id $API_ID \
--authorizer-id $AUTHORIZER_ID \
--authorizer-result-ttl-in-seconds 300
7-2. identitySource キャッシュキー設計
キャッシュキーは identity_sources フィールドで決定される。キャッシュキーの選択が誤ると、異なるユーザーに同じポリシーが返る重大なセキュリティインシデントが発生する。
| identitySource 設定例 | キャッシュキー | 注意点 |
|---|---|---|
$request.header.Authorization | Authorization ヘッダー全体 | 最も一般的。Bearer Token 全体がキー |
$request.header.Authorization, $context.identity.sourceIp | トークン + 発信元 IP | IP ローテーション時にキャッシュ不命中増加 |
$request.querystring.token | クエリパラメータ | URL にトークン露出するため非推奨 |
$request.header.X-API-Key | API キーヘッダー | マシン間通信・サービスアカウント向き |
マルチテナント構成での複合キー設定例:
resource "aws_apigatewayv2_authorizer" "request_multi_key" {
api_id = aws_apigatewayv2_api.http_api.id
authorizer_type = "REQUEST"
authorizer_uri= aws_lambda_function.authorizer.invoke_arn
name = "request-authorizer-multitenant"
# テナント分離のためにテナント ID もキャッシュキーに含める
identity_sources = [
"$request.header.Authorization",
"$request.header.X-Tenant-ID"
]
authorizer_result_ttl_in_seconds = 300
authorizer_payload_format_version = "2.0"
}
キャッシュキーに含めるフィールドが多いほどキャッシュ命中率は下がる。最小限の識別子のみキャッシュキーに含めること。
7-3. QG-4: Caching + identitySource 設計の重要ポイント
- TTL ≠ トークン有効期限: TTL はキャッシュ保持時間。JWT の exp クレームより短く設定する (例: JWT 有効期限 1h → TTL 300s でトークン失効前に確実に再検証)
- キャッシュキー = セキュリティ境界: identitySource に含まれないフィールドはキャッシュ区別に使われない。マルチテナントなら X-Tenant-ID もキーに含める。含め忘れると別テナントの認可結果が流用される
- キャッシュ無効化のタイミング: トークン失効・ロール変更・緊急アクセス停止時もキャッシュが残存する。緊急時は TTL=0 に変更後ステージを再デプロイするか、Authorizer を再作成してキャッシュをフラッシュする
- TOKEN Authorizer の identitySource 制約: TOKEN Authorizer は Authorization ヘッダー 1 フィールドのみ指定可。複合キーが必要な場合は REQUEST Authorizer を使用する
- ワイルドカードポリシー (*) のキャッシュ危険性: Lambda が
arn:aws:execute-api:*:*:*を返す実装では 1 ユーザーの認可結果が全ルートに適用される。セキュリティ要件に応じてルート単位の明示的なポリシーを検討する
7-4. AWS WAF Web ACL + HTTP API 統合
AWS WAF (Web Application Firewall) を HTTP API の前段に配置することで、DDoS・SQLインジェクション・XSS・bot 攻撃を API Gateway レイヤーで防御できる。
HTTP API への WAF 適用手順 (コンソール):
- WAF コンソール → 「Web ACL を作成」
- リソースタイプ:
Regional resources(HTTP API は REGIONAL。CLOUDFRONT は不可) - ルール追加:
AWSManagedRulesCommonRuleSet+AWSManagedRulesSQLiRuleSet+ Rate-based ルール - Web ACL 作成完了後 → 「Associated AWS resources」→「Add AWS resources」
- 「Amazon API Gateway」 を選択 → HTTP API のステージを選択して関連付け
resource "aws_wafv2_web_acl" "http_api_waf" {
name = "${var.project}-http-api-waf"
scope = "REGIONAL"
default_action {
allow {}
}
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 1
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CommonRuleSet"
sampled_requests_enabled= true
}
}
rule {
name = "RateLimitRule"
priority = 2
action {
block {}
}
statement {
rate_based_statement {
limit = 2000
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimit"
sampled_requests_enabled= true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "${var.project}-http-api-waf"
sampled_requests_enabled= true
}
}
resource "aws_wafv2_web_acl_association" "http_api" {
resource_arn = aws_apigatewayv2_stage.default.arn
web_acl_arn = aws_wafv2_web_acl.http_api_waf.arn
depends_on = [
aws_apigatewayv2_stage.default,
aws_wafv2_web_acl.http_api_waf
]
}
# WAF 関連付け状態を確認
aws wafv2 list-resources-for-web-acl \
--web-acl-arn $WAF_ACL_ARN \
--scope REGIONAL \
--region ap-northeast-1
# HTTP API ステージの ARN を取得
aws apigatewayv2 get-stage \
--api-id $API_ID \
--stage-name '$default' \
--query "Arn" \
--output text
7-5. WAF 紐付け順ミスの落とし穴
よくあるミス: aws_wafv2_web_acl_association の resource_arn に誤った ARN 形式を指定すると関連付けが失敗する。
| 項目 | 正しい形式 | 誤りやすい形式 |
|---|---|---|
| ステージ ARN | arn:aws:apigateway:{region}::/apis/{api-id}/stages/$default | arn:aws:execute-api:{region}:{account}:{api-id}/$default/* (execute-api ARN は別用途) |
| WAF Scope | REGIONAL (HTTP API / ALB / AppSync) | CLOUDFRONT (CloudFront のみ有効。us-east-1 で作成が必要) |
| Terraform depends_on | aws_apigatewayv2_stage への明示的な依存が必要 | 依存が欠如するとステージ作成前に association が実行されてエラー |
ステージ ARN の正確な取得方法:
# Terraform output から取得 (推奨)
terraform output http_api_stage_arn
# AWS CLI で直接取得
aws apigatewayv2 get-stage \
--api-id $(aws apigatewayv2 get-apis \
--query "Items[?Name=='${API_NAME}'].ApiId" \
--output text) \
--stage-name '$default' \
--query "Arn" \
--output text
7-6. QG-5: 落とし穴10選 + WAF 統合まとめ
- 1. JWT issuer 末尾スラッシュ: Cognito の issuer は
https://cognito-idp.{region}.amazonaws.com/{userPoolId}/— 末尾スラッシュ必須。スラッシュがないと 401 Unauthorized (issuer mismatch) - 2. WAF Scope CLOUDFRONT を REGIONAL に使用: HTTP API は REGIONAL WAF のみ対応。CLOUDFRONT スコープで作成した Web ACL は HTTP API に紐付け不可 (us-east-1 でしか作れない)
- 3. Caching identitySource テナント漏れ: マルチテナント環境で X-Tenant-ID をキャッシュキーに含め忘れると、テナント A の認可結果がテナント B に返る重大セキュリティインシデント
- 4. TOKEN Authorizer の identitySource 複数指定: TOKEN Authorizer は Authorization ヘッダー 1 フィールドのみ。複合キー認証は REQUEST Authorizer を使用する
- 5. Lambda IAM ポリシー形式エラー:
Version: "2012-10-17"が欠落、または Action がexecute-api:Invokeでなくapigateway:GETになっている場合に 500 エラー - 6. authorizer_payload_format_version 未設定: デフォルト
1.0では Lambda が旧形式 event を受け取る。2.0を明示しないと requestContext の JWT クレームが取得できない - 7. WAF ステージ ARN フォーマット誤り: execute-api ARN (
arn:aws:execute-api:...) ではなく apigateway ARN (arn:aws:apigateway:...) を resource_arn に指定する - 8. TTL とトークン有効期限の逆転: TTL をトークン有効期限より長く設定すると失効トークンでも認可が通る。TTL はトークン有効期限の半分以下を推奨
- 9. Auth0 audience 不一致: Auth0 で発行された JWT の
audクレームが API Identifier と一致しないと 401。Auth0 Application の Audiences 設定 (Client ID ではなく API Identifier URL) を確認 - 10. SIMPLE レスポンス形式の誤解:
authorizer_payload_format_version = "2.0"では Lambda が{"isAuthorized": true}を返すだけでよい。1.0の IAM ポリシー形式を混在させると動作しない
7-7. 観測性: CloudWatch Logs + X-Ray トレーシング + Access Log 形式
CloudWatch Logs アクセスログ設定:
resource "aws_cloudwatch_log_group" "api_access" {
name = "/aws/apigateway/${var.project}-access"
retention_in_days = 30
}
resource "aws_apigatewayv2_stage" "default" {
api_id= aws_apigatewayv2_api.http_api.id
name = "$default"
auto_deploy = true
access_log_settings {
destination_arn = aws_cloudwatch_log_group.api_access.arn
format = jsonencode({
requestId = "$context.requestId"
ip= "$context.identity.sourceIp"
requestTime= "$context.requestTime"
httpMethod = "$context.httpMethod"
routeKey= "$context.routeKey"
status = "$context.status"
responseLength= "$context.responseLength"
authorizerError = "$context.authorizer.error"
authLatency= "$context.authorizer.latency"
integrationError = "$context.integrationErrorMessage"
})
}
default_route_settings {
detailed_metrics_enabled = true
throttling_burst_limit= 5000
throttling_rate_limit = 10000
}
}
# Authorizer エラーを CloudWatch Insights で分析
aws logs start-query \
--log-group-name "/aws/apigateway/${PROJECT}-access" \
--start-time $(date -d '1 hour ago' +%s) \
--end-time $(date +%s) \
--query-string \
'fields @timestamp, status, authorizerError
| filter authorizerError != ""
| stats count() by authorizerError
| sort count desc'
X-Ray トレーシング有効化:
resource "aws_apigatewayv2_stage" "default" {
# ... 上記設定に追加
default_route_settings {
detailed_metrics_enabled = true
data_trace_enabled = true
}
}
主要 CloudWatch Metrics 監視設計:
| メトリクス | 名前空間 | 監視目的 |
|---|---|---|
4XXError | AWS/ApiGateway | Authorizer 認証失敗率の追跡 |
5XXError | AWS/ApiGateway | Authorizer Lambda クラッシュ・内部エラー |
Latency | AWS/ApiGateway | p99 レイテンシ (Caching 効果測定) |
IntegrationLatency | AWS/ApiGateway | Backend Lambda レイテンシ |
Count | AWS/ApiGateway | リクエスト総数 (スケーリング判断) |
resource "aws_cloudwatch_metric_alarm" "authorizer_5xx" {
alarm_name = "${var.project}-authorizer-5xx-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name= "5XXError"
namespace = "AWS/ApiGateway"
period = 60
statistic = "Sum"
threshold = 10
dimensions = {
ApiId = aws_apigatewayv2_api.http_api.id
Stage = "$default"
}
alarm_actions = [aws_sns_topic.alert.arn]
}
8. まとめ + よくある落とし穴10選 + 次回予告
8-1. 本記事の全体チートシート
Terraform リソース早見表:
| リソース | 用途 |
|---|---|
aws_apigatewayv2_api | HTTP API 本体 (protocol_type = "HTTP") |
aws_apigatewayv2_authorizer | Authorizer 定義 (TOKEN / REQUEST / JWT) |
aws_apigatewayv2_route | ルート定義 (authorization_type 指定) |
aws_apigatewayv2_integration | Backend Lambda 統合 |
aws_apigatewayv2_stage | デプロイステージ ($default 推奨) |
aws_lambda_function | Authorizer Lambda 本体 |
aws_lambda_permission | API Gateway → Lambda 呼出許可 |
aws_wafv2_web_acl | WAF Web ACL (scope = "REGIONAL") |
aws_wafv2_web_acl_association | WAF と HTTP API ステージの紐付け |
AWS CLI 主要コマンドチートシート:
# HTTP API 作成
aws apigatewayv2 create-api \
--name my-http-api \
--protocol-type HTTP
# JWT Authorizer 作成 (Cognito — issuer 末尾スラッシュ必須)
aws apigatewayv2 create-authorizer \
--api-id $API_ID \
--authorizer-type JWT \
--identity-source '$request.header.Authorization' \
--name jwt-authorizer \
--jwt-configuration \
"Audience=$AUDIENCE,Issuer=https://cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}/"
# Authorizer 一覧取得
aws apigatewayv2 get-authorizers --api-id $API_ID
# WAF と API ステージの関連付け確認
aws wafv2 list-resources-for-web-acl \
--web-acl-arn $WAF_ACL_ARN \
--scope REGIONAL \
--region ap-northeast-1
# Authorizer キャッシュ TTL 確認
aws apigatewayv2 get-authorizer \
--api-id $API_ID \
--authorizer-id $AUTHORIZER_ID \
--query "AuthorizerResultTtlInSeconds"
8-2. よくある落とし穴10選 (症状・原因・対処)
- 1. 401: Cognito issuer 末尾スラッシュ欠落 — Cognito JWT の
issは末尾スラッシュありで発行される。API Gateway の issuer 設定と完全一致しないと 401。末尾スラッシュを必ず付与する - 2. 403: WAF スコープ誤り — HTTP API は REGIONAL WAF のみ。CloudFront 用 WAF (CLOUDFRONT スコープ・us-east-1) を関連付けようとするとエラー
- 3. 500: Lambda IAM ポリシー形式エラー —
Version: "2012-10-17"欠落または Effect/Action/Resource の不正。CloudWatch Logs で詳細を確認 - 4. キャッシュによる失効トークン通過 — TTL をトークン有効期限より長く設定すると失効トークンが通る。TTL はトークン有効期限の半分以下を推奨
- 5. 500: authorizer_payload_format_version 未指定 — デフォルト 1.0 のまま SIMPLE レスポンス (
{"isAuthorized": true}) を返すと 500 エラー。2.0 を明示指定する - 6. aws_lambda_permission の source_arn 誤り — source_arn には execute-api パターン
arn:aws:execute-api:{region}:{account}:{api-id}/*/*が正しい - 7. WAF resource_arn フォーマット誤り —
aws_wafv2_web_acl_associationの resource_arn には apigateway ARN (arn:aws:apigateway:...) を指定。execute-api ARN は不可 - 8. Auth0 audience 不一致 — Auth0 トークンの
audは Application Client ID ではなく API Identifier (URL 形式)。Auth0 ダッシュボードで API Identifier を確認 - 9. TOKEN Authorizer で複合キー使用 — TOKEN Authorizer は Authorization ヘッダー 1 フィールドのみ。X-Tenant-ID 等の追加フィールドが必要なら REQUEST Authorizer に変更
- 10. マルチテナントのキャッシュキー漏れ — identitySource に X-Tenant-ID を含め忘れると、テナント A の認可結果がテナント B に流用される。設計レビューとコードレビューの両方で確認必須
8-3. JWT デバッグクイックリファレンス
本番障害の多くは JWT の issuer・audience・有効期限・署名アルゴリズムの不一致に起因する。以下のコマンドでトークンを即座に診断できる。
# JWT のペイロードを Base64 デコードして確認 (署名検証なし — 診断用)
python3 -c "
import base64, json, sys
token = sys.argv[1]
header, payload, sig = token.split('.')
# Base64 パディング補完
decoded = base64.b64decode(payload + '==').decode()
print(json.dumps(json.loads(decoded), indent=2, ensure_ascii=False))
" <YOUR_JWT_TOKEN>
# Cognito issuer の確認 (末尾スラッシュあり)
aws cognito-idp describe-user-pool \
--user-pool-id $USER_POOL_ID \
--query "UserPool.{Issuer:join('', ['https://cognito-idp.', UserPoolId, '.auth.us-east-1.amazoncognito.com/'])}" \
--output text
# API Gateway に設定された Authorizer の issuer を確認
aws apigatewayv2 get-authorizer \
--api-id $API_ID \
--authorizer-id $AUTHORIZER_ID \
--query "JwtConfiguration.Issuer" \
--output text
# Authorizer エラーログをリアルタイム確認
aws logs tail /aws/apigateway/${PROJECT}-access \
--follow \
--filter-pattern '{ $.status = 401 || $.status = 403 }'
Authorizer 設計の意思決定フロー:
認証基盤は何か?
├─ Cognito User Pools / Auth0 / Okta (標準 JWT)
│ └─ JWT Authorizer (Lambda 不要) → §4-5 参照
│
└─ 独自トークン / API キー / 複合条件
├─ Authorization ヘッダー 1 個のみ → TOKEN Authorizer → §4-3 参照
└─ ヘッダー/クエリ/IP 複合 → REQUEST Authorizer → §4-4 参照
キャッシュが必要か?
├─ 短命トークン (< 5 分) / セッション → TTL=0
└─ 長命トークン (> 5 分) → TTL = トークン有効期限 / 2
マルチテナント?
├─ YES → identitySource に X-Tenant-ID を追加
└─ NO → Authorization ヘッダーのみで OK
8-4. 関連公式ドキュメント・参考リンク
AWS 公式: HTTP API Lambda Authorizer ガイド
Terraform Registry: aws_apigatewayv2_authorizer
AWS 公式: AWS WAF Developer Guide
8-5. 本サイト関連記事
| 記事 | テーマ |
|---|---|
| Cognito + Salesforce mTLS 連携 | Cognito を IdP として mTLS 認証を構成する応用パターン |
| Bedrock Agents 本番運用ガイド | Lambda Authorizer で保護した HTTP API を Bedrock Action Group に利用するパターン |
| CloudFront WAF Bot Control 実装 | §7 WAF 統合の上流に位置する CloudFront + WAF 詳細実装 |
| CloudFront × WAF v2 × Shield 多層防御 本番運用完全ガイド | WAF v2 Managed Rules 全 8 セット + Shield Advanced + Athena ログ分析を完全網羅した上位ガイド |
| WAF + CloudFront Sorryページ完全実装ガイド | Lambda Authorizer の 401/403 Deny 時のエラーレスポンス設計と Lambda@Edge Sorry 実装(方式C)を組み合わせると応用範囲が広がる |
8-6. 次回予告
TODO: 次回記事 (API Gateway WebSocket API + Lambda による双方向通信本番設計、または AppSync + Cognito によるリアルタイム GraphQL 認証)