API Gateway Lambda Authorizer 本番運用 HTTP API JWT Cognito

目次

1. この記事について

fig01: API Gateway HTTP API + Lambda Authorizer 全体像

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軸

この記事でしか得られない 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 APIAuthorizer の概念を知っていること
LambdaPython or Node.js でハンドラを書けること
Terraformresource / variable / output が書けること
Cognito または Auth0JWT (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 認証認可の実装に特化した記事です。

AWS 公式: HTTP API Lambda Authorizer ドキュメント

QG-1: API Gateway 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. 前提・環境・準備

fig02: 検証環境 + IAM 構成

本章では検証に必要なローカル環境・AWS 権限・IAM 構成・Terraform ワークスペースを整備します。§3 以降のハンズオンはすべてこの環境を前提とします。

2-1. 前提環境

ツール / サービスバージョン用途
AWS アカウントHTTP API・Lambda・Cognito・WAF を作成
AWS CLIv2.15 以上リソース確認・CLI 手順実行
Terraform1.9.x 以上IaC 実装 (aws_apigatewayv2_ / aws_lambda_)
Python3.11 以上Lambda Authorizer ロジック (TOKEN/REQUEST)
Node.js20.x 以上代替 Lambda runtime (選択可)
cURL7.x 以上エンドポイントテスト
jq1.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 APIaws_apigatewayv2_apiHTTP API 本体
API Gateway Authorizeraws_apigatewayv2_authorizerTOKEN/REQUEST/JWT 認証
API Gateway Routeaws_apigatewayv2_routeルーティング + Authorizer 紐付け
API Gateway Integrationaws_apigatewayv2_integrationBackend Lambda 統合
API Gateway Stageaws_apigatewayv2_stageデプロイステージ ($default)
Lambdaaws_lambda_functionAuthorizer ロジック + Backend
IAM Roleaws_iam_roleLambda 実行ロール
IAM Policyaws_iam_role_policy_attachmentLambda 基本実行権限
Cognito User Poolaws_cognito_user_poolJWT IdP (Cognito 選択時)
WAF v2 Web ACLaws_wafv2_web_acl前段フィルタ (Regional)
CloudWatch Logsaws_cloudwatch_log_groupアクセスログ + Authorizer ログ
X-Raytracing_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 設定確認手順

  1. AWS マネジメントコンソール → API Gateway を開く
  2. 左ペイン「APIs」→「HTTP API」タブで対象 API を選択
  3. 左メニュー「Authorization」→「Manage authorizers」タブ
  4. 作成済み Authorizer の一覧と詳細設定 (IdentitySource / TTL / AuthorizerPayloadFormatVersion) を確認
  5. 「Routes」タブで各ルートにどの Authorizer が紐付いているかを確認

3. REST API vs HTTP API 選択基準 (コスト・機能・レイテンシ比較)

全体アーキテクチャ再確認: HTTP API + Lambda Authorizer の構成

  • 本記事の構成: 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 判定フローで確認

fig03: REST API vs HTTP API 機能差分マッピング

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 reqHTTP API が 71% 安い
料金 (300M〜1B req/月)$1.51 / 1M req$0.90 / 1M reqHTTP 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 onlyCDN 前段が必要なら 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 APIHTTP API は未対応
リクエスト Body 検証 (JSON Schema)REST APIHTTP API はサポートなし
エッジ最適化 (CloudFront 自動統合)REST APIHTTP API は Regional のみ
X-Ray 詳細セグメントトレースREST APIHTTP 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

コンソール手順

  1. AWS マネジメントコンソール → API Gateway → 「API を作成」をクリック
  2. API タイプで 「HTTP API」 を選択し「構築」をクリック
  3. 「統合を追加」でバックエンド Lambda 関数を指定 (後から追加も可能)
  4. API 名 (例: my-http-api) を入力し「次へ」をクリック
  5. ルートの追加画面でメソッド・パス・統合を設定し「次へ」をクリック
  6. ステージ名を prod「自動デプロイ」をオンにして「次へ」→「作成」
  7. 作成後、左メニュー「CORS」からオリジン・メソッド・ヘッダーを設定し「保存」
  8. 「デプロイ」をクリックし、画面上部のエンドポイント 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)

fig04: Authorizer 3種フロー比較 (TOKEN/REQUEST/JWT)

HTTP API における認証認可の核心は Authorizer 種別の選択設計にある。TOKEN・REQUEST・JWT の3種はユースケースと制約が異なり、誤選択はパフォーマンス劣化・セキュリティリスク・コスト増大に直結する。

4-1. Authorizer 3種 概要と選択フロー

項目TOKEN AuthorizerREQUEST AuthorizerJWT Authorizer
対応 APIREST / HTTP APIREST / HTTP APIHTTP API 専用
identitySourceAuthorization ヘッダー 1個ヘッダー/クエリ/パス/コンテキストAuthorization ヘッダー (JWT)
Lambda 実行毎リクエスト (キャッシュ可)毎リクエスト (キャッシュ可)不要 (APIGW 直接検証)
キャッシュauthorizerResultTtlInSecondsauthorizerResultTtlInSecondsN/A
主なユースケース独自 Bearer トークン検証IP + ヘッダー + クエリ複合判定OIDC/OAuth2 標準 JWT 検証
コストLambda 実行費 + APIGWLambda 実行費 + APIGWAPIGW のみ (最安)
実装複雑度低〜中中〜高低 (設定のみ)

選択フロー:

  • JWT を OIDC/OAuth2 標準仕様で検証するだけでよい → JWT Authorizer (Lambda 不要・最安・最速)
  • Cognito User Pools / Auth0 / Okta が IdP なら第一候補
  • カスタム検証ロジックが必要で Authorization ヘッダーのみで判定できる → TOKEN Authorizer
  • IP アドレス・複数ヘッダー・クエリパラメータの組み合わせで判定 → REQUEST Authorizer

4-2. QG-2: Authorizer 3種リクエストフロー比較

QG-2: Authorizer 3種リクエストフロー完全比較 (TOKEN / REQUEST / JWT)

◆ 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: Deny Policy → 403 (挙動が異なる)
  • context に格納した値は $context.authorizer.userId でバックエンドから参照可能
  • Resource に /*/* を使うとキャッシュ効率が向上するが、Route 個別の Deny 制御は失われる

4-4. REQUEST Authorizer 実装 (複合条件判定)

REQUEST Authorizer は event.headersevent.queryStringParametersevent.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 PoolsAuth0
issuerhttps://cognito-idp.{region}.amazonaws.com/{UserPoolId}/https://{tenant}.auth0.com/
audience["{AppClientId}"]["https://api.example.com"]
identitySource$request.header.Authorization$request.header.Authorization
落とし穴: JWT issuer 末尾スラッシュ問題

  • 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}"

コンソール操作

  1. API Gateway コンソール → 対象の HTTP API を選択
  2. 左ペイン「Authorization」→「Manage authorizers」タブ → 「Create」
  3. Authorizer type を選択: Lambda (TOKEN/REQUEST) または JWT
  4. Lambda 選択時: Lambda 関数 ARN・Payload format version 2.0・TTL・Identity sources を入力
  5. JWT 選択時: Issuer URL (末尾スラッシュ必須)・Audience・Identity source を入力
  6. 作成後: 左ペイン「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 専用で、issueraudience を指定するだけで 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 AuthorizerHTTP API JWT AuthorizerLambda Authorizer
Lambda 実行なしなしあり
対応 IdPCognito のみCognito/Auth0/Okta/任意 OIDC任意
API 種別REST API のみHTTP API のみREST/HTTP 両対応
カスタムロジック不可不可可 (外部 DB/複数 IdP)
Cachingなしなしあり (TTL 0-3600s)
追加コストなしなしLambda 実行分
JWKs 自動取得Cognito 統合のみjwks_uri 指定自実装
推奨シナリオREST API + CognitoHTTP 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 統合パターン

QG-3: Cognito vs Auth0/Okta — issuer/audience/jwks_uri 設定差分と採用判断

HTTP API 内蔵 JWT Authorizer を使う場合、IdP によって issuer と jwks_uri の形式が異なる。誤設定は全エンドポイントで 401 が返り続ける障害に直結する。

IdPissuer (JWT Authorizer 設定値)jwks_uri (API GW が自動導出)audience
Cognito User Poolshttps://cognito-idp.{region}.amazonaws.com/{UserPoolId}
(末尾スラッシュなし)
issuer + /.well-known/jwks.jsonApp Client ID
Auth0https://{tenant}.auth0.com/
(末尾スラッシュ必須)
issuer + .well-known/jwks.jsonAPI Identifier (例: https://api.example.com)
Oktahttps://{domain}/oauth2/{authServerId}
(Default の場合 /oauth2/default)
issuer + /v1/keysAPI 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 が社内標準化されている場合に採用。

fig05: Cognito User Pools vs Auth0/Okta 統合パターン

5-3. Cognito User Pools + HTTP API JWT Authorizer 統合実装

HTTP API の JWT Authorizer で Cognito を使う場合、jwt_configurationissuer に 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 形式
Defaulthttps://{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点セット まとめ

操作TerraformAWS CLIコンソール
JWT Authorizer 作成aws_apigatewayv2_authorizer (type=JWT)create-authorizer --authorizer-type JWTAuthorization → Create (JWT)
issuer 設定jwt_configuration.issuer--jwt-configuration Issuer=...Issuer URL フィールド
audience 設定jwt_configuration.audience--jwt-configuration Audience=[...]Audience フィールド
Authorizer 確認terraform showget-authorizer --api-id $ID --authorizer-id $AIDManage authorizers
動作テストterraform applycurl -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 例 (よくある設定ミス)

よくある設定ミス4選

  1. HTTP API で authorizer_type = “TOKEN” を指定する
    TOKEN タイプは REST API 専用。HTTP API では authorizer_type = "REQUEST" または "JWT" のみ有効。
    エラー例: ValidationException: Invalid authorizer type TOKEN for HTTP API
  2. authorizer_payload_format_version の省略
    HTTP API の Lambda Authorizer では必須フィールド。省略すると 1.0 フォーマット (REST API 互換) が適用され、イベント構造が変わり Lambda が正常動作しない。HTTP API では "2.0" を必ず指定。
  3. enable_simple_responses = true なのに IAM Policy Document を返す
    enable_simple_responses = true の場合、Lambda は {"isAuthorized": true} 形式のみ受け付ける。{"principalId": ..., "policyDocument": {...}} を返すと 500 Internal Server Error になる。
  4. JWT issuer の末尾スラッシュを統一しない
    Cognito: 末尾スラッシュなし、Auth0: 末尾スラッシュあり。逆に設定すると全エンドポイントで 401 が返り続ける。
    確認コマンド: aws apigatewayv2 get-authorizer --api-id $ID --authorizer-id $AID --query 'JwtConfiguration.Issuer'

7. Caching 戦略 + WAF 統合 + 観測性

fig06: 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.AuthorizationAuthorization ヘッダー全体最も一般的。Bearer Token 全体がキー
$request.header.Authorization, $context.identity.sourceIpトークン + 発信元 IPIP ローテーション時にキャッシュ不命中増加
$request.querystring.tokenクエリパラメータURL にトークン露出するため非推奨
$request.header.X-API-KeyAPI キーヘッダーマシン間通信・サービスアカウント向き

マルチテナント構成での複合キー設定例:

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 設計の重要ポイント

Caching + identitySource 設計: 本番で押さえるべき5点

  • 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 適用手順 (コンソール):

  1. WAF コンソール → 「Web ACL を作成」
  2. リソースタイプ: Regional resources (HTTP API は REGIONAL。CLOUDFRONT は不可)
  3. ルール追加: AWSManagedRulesCommonRuleSet + AWSManagedRulesSQLiRuleSet + Rate-based ルール
  4. Web ACL 作成完了後 → 「Associated AWS resources」→「Add AWS resources」
  5. 「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_associationresource_arn に誤った ARN 形式を指定すると関連付けが失敗する。

項目正しい形式誤りやすい形式
ステージ ARNarn:aws:apigateway:{region}::/apis/{api-id}/stages/$defaultarn:aws:execute-api:{region}:{account}:{api-id}/$default/* (execute-api ARN は別用途)
WAF ScopeREGIONAL (HTTP API / ALB / AppSync)CLOUDFRONT (CloudFront のみ有効。us-east-1 で作成が必要)
Terraform depends_onaws_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 統合まとめ

API Gateway HTTP API + Lambda Authorizer 本番落とし穴10選

  • 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 監視設計:

メトリクス名前空間監視目的
4XXErrorAWS/ApiGatewayAuthorizer 認証失敗率の追跡
5XXErrorAWS/ApiGatewayAuthorizer Lambda クラッシュ・内部エラー
LatencyAWS/ApiGatewayp99 レイテンシ (Caching 効果測定)
IntegrationLatencyAWS/ApiGatewayBackend Lambda レイテンシ
CountAWS/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_apiHTTP API 本体 (protocol_type = "HTTP")
aws_apigatewayv2_authorizerAuthorizer 定義 (TOKEN / REQUEST / JWT)
aws_apigatewayv2_routeルート定義 (authorization_type 指定)
aws_apigatewayv2_integrationBackend Lambda 統合
aws_apigatewayv2_stageデプロイステージ ($default 推奨)
aws_lambda_functionAuthorizer Lambda 本体
aws_lambda_permissionAPI Gateway → Lambda 呼出許可
aws_wafv2_web_aclWAF Web ACL (scope = "REGIONAL")
aws_wafv2_web_acl_associationWAF と 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選 (症状・原因・対処)

本番投入前に必ず確認: 落とし穴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 認証)