- 1 Amazon Cognito × Salesforce Apex — mTLSセキュアユーザー管理ハンズオン
- 1.1 目次
- 1.2 Section 1: 概念編 — Cognito / mTLS / OAuth 2.0 を正確に理解する
- 1.3 Section 2: アーキテクチャ + AWSコンソール操作
- 1.4 Section 3: mTLSハンズオン(Step A-B)
- 1.5 Step A: mTLS証明書生成
- 1.6 Step B: AWS環境構築
- 1.7 Section 4: Terraform IaC編
- 1.8 Section 5: Salesforce Apex から Cognito 保護 API を呼び出す
- 1.9 Section 6: セキュリティTips + トラブルシューティング
- 1.10 Section 7: クリーンアップ + まとめ
- 1.11 おわりに
Amazon Cognito × Salesforce Apex — mTLSセキュアユーザー管理ハンズオン
公開日: 2026-04-13 / 難易度: 上級 / 所要時間: 約150分
この記事で学ぶこと
– Amazon Cognito User Pool / Identity Pool の役割の違いと M2M 認証の仕組み
– mTLS(相互TLS認証)の概念・証明書チェーン・TrustStore の構造
– OAuth 2.0 Client Credentials フローを使った Salesforce → AWS サービス間通信
– OpenSSL による CA 証明書・クライアント証明書の生成手順
– Terraform で Cognito + Lambda + API Gateway(mTLS + JWT Authorizer)を一括構築
– Salesforce Apex(Named Credential + CognitoAuthService + CognitoApiService)の実装前提条件
– AWS アカウント(IAM 管理者権限推奨)
– Salesforce Developer Edition または Sandbox 環境
– OpenSSL 3.x インストール済み
– Terraform 1.7.0 以上インストール済み
– AWS CLI v2 インストール・認証情報設定済み
目次
- Section 1: 概念編 — Cognito / mTLS / OAuth 2.0 を正確に理解する
- Section 2: アーキテクチャ + AWSコンソール操作
- Section 3: mTLSハンズオン(Step A-B)
- Section 4: Terraform IaC編
- Section 5: Salesforce Apex連携
- Section 6: セキュリティTips + トラブルシューティング
- Section 7: クリーンアップ + まとめ
Section 1: 概念編 — Cognito / mTLS / OAuth 2.0 を正確に理解する
エンタープライズ統合において「APIを安全に呼ぶ」という要件は当たり前になったが、「二重保護」を実現しているシステムは少ない。本記事では Amazon Cognito(JWT認証)+ mTLS(クライアント証明書) の二段構えでAPIを守り、Salesforce Apex から安全に呼び出す実装を Terraform でゼロから構築する。
本セクションは実装に入る前の概念整理だ。Cognito の2大機能・mTLS の仕組み・OAuth 2.0 Client Credentials フローを正確に理解してから手を動かすことで、トラブル発生時の原因切り分けが格段に速くなる。
1-1. Amazon Cognito の全体像
Amazon Cognito は User Pool と Identity Pool という2つの独立したサービスで構成される。混同が最も多いポイントなので、先に表で整理する。
| 機能 | User Pool | Identity Pool |
|---|---|---|
| 役割 | 認証(Authentication) | 認可(Authorization) |
| 発行物 | JWT(id_token / access_token / refresh_token) | AWS一時認証情報(STS) |
| 主な用途 | ユーザー登録・ログイン・MFA・パスワードリセット | AWSリソース(S3, DynamoDB等)への直接アクセス |
| 相互依存 | 単独動作可 | User Pool と組み合わせ可(またはGoogle/Facebook等) |
| 本記事での役割 | ✅ メイン(M2M認証・JWT発行) | 参考紹介のみ |
一言で言えば:User Pool は「誰か?」を確認し、Identity Pool は「何をしていいか?」をAWS権限として付与する。
本記事のメインターゲットは User Pool だ。Salesforce → AWS 間の M2M 通信には JWT を使用し、Identity Pool(STS連携)は使わない。
1-2. Cognito User Pool の詳細
ユーザーディレクトリの仕組み
User Pool はマネージドのユーザーディレクトリだ。各ユーザーには以下のスキーマ属性が付与できる。
| 属性タイプ | 例 | 変更可否 |
|---|---|---|
| 標準属性 | email, phone_number, name | ✅ |
| カスタム属性 | custom:tenant_id, custom:role | ✅(読み取り専用設定可) |
| 読み取り専用 | sub(ユニークID), cognito:username | ❌ |
認証フロー の種類
User Pool には3種類の認証フローがある。
USER_PASSWORD_AUTH
ユーザー名とパスワードをそのまま送信。シンプルだが、ネットワーク上でパスワードが平文に近い形で送られるため、本番では非推奨。USER_SRP_AUTH(SRP: Secure Remote Password)
パスワードを直接送らず、数学的な proof を交換してサーバーがパスワードを検証する。MITMに強く、本番推奨。CUSTOM_AUTH(Lambda trigger経由)
Pre-Authentication Lambda を使って任意の認証ロジックを実装できる。SMS OTP・TOTP・生体認証のカスタム実装などに使用。
JWTトークンの構造
Cognito が発行するJWTは ヘッダー.ペイロード.署名 の3パートで構成される。
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9← ヘッダー(Base64URL)
.eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoxNzA... ← ペイロード(Base64URL)
.SflKxwRJSMeKKF2QT4fwpMeJf36P... ← 署名(RS256)
| トークン種別 | 用途 | 有効期限(デフォルト) |
|---|---|---|
id_token | ユーザー情報(sub, email等)を含む | 1時間 |
access_token | APIアクセス認可(scopeを含む) | 1時間 |
refresh_token | 上記2つの更新 | 30日 |
本記事の M2M フローでは access_token のみが発行・使用される(ユーザーが存在しないため id_token は不要)。
App Client の役割
App Client は User Pool へのアクセス窓口となるエンティティだ。
- クライアントID(公開): アプリケーションを識別する
- クライアントシークレット(秘密): M2M / サーバーサイドアプリで使用
一つの User Pool に複数の App Client を作成できる。例えば「Webアプリ用(シークレットなし)」「Salesforce連携用(シークレットあり)」を分けて管理するのがベストプラクティスだ。
Lambda Trigger(主要3種)
| トリガー | タイミング | 活用例 |
|---|---|---|
| Pre-authentication | 認証処理の直前 | IPブロック・不正ログイン検知 |
| Post-confirmation | ユーザー確認完了直後 | DynamoDBへのユーザーレコード作成 |
| Pre-token-generation | トークン発行直前 | JWTペイロードへのカスタムクレーム追加 |
1-3. Cognito Identity Pool の詳細
本記事では直接使用しないが、User Pool と混同しがちなため概念を整理する。
Federated Identity の仕組み
Identity Pool は様々な IdP(Identity Provider)からのトークンを受け取り、AWS一時認証情報に変換するブリッジだ。
対応 IdP:
- Amazon Cognito User Pool(JWT)
- Google / Facebook / Apple(OIDC)
- SAML 2.0 プロバイダ
- 開発者認証済みID(カスタムバックエンド)
GetId → GetCredentialsForIdentity フロー
クライアント
│
│ ① 外部IdPでサインイン → id_token 取得
│
├─[GetId]──────────────────────► Identity Pool
││
│ ◄── IdentityId 返却 ───────────────┘
│
├─[GetCredentialsForIdentity]──► Identity Pool
│ (IdentityId + id_token)│
│├─► STS (AssumeRoleWithWebIdentity)
│ ◄── AWS一時認証情報 ────────────────┘
│ (AccessKeyId / SecretKey /
│ SessionToken / Expiration)
│
└─► S3 / DynamoDB / etc. へ直接アクセス
認証済みロール vs 未認証ロール
| ロール | 対象 | IAMポリシーの典型例 |
|---|---|---|
| 認証済みロール | サインイン済みユーザー | S3特定バケットの読み書き |
| 未認証ロール(ゲスト) | 未サインインユーザー | S3公開コンテンツの読み取りのみ |
STSによる一時認証情報のデフォルト有効期限は 1時間(Identity Pool側で15分〜12時間の間で設定変更可能)。
1-4. mTLS(Mutual TLS)の仕組み
通常のTLSとmTLSの違い
通常のHTTPS通信ではサーバーのみが証明書を提示し、クライアントは「このサーバーは本物か?」だけを検証する。
mTLS ではクライアントも証明書を提示し、サーバーが「このクライアントは信頼できるか?」を検証する。これが「相互(Mutual)」の意味だ。
【通常のTLS】
クライアント ──→ サーバー証明書を要求
クライアント ←── サーバー証明書(CA署名済み)を提示
クライアントサーバー証明書を検証 ✅
↓
通信開始(クライアントの身元は未検証)
【mTLS(相互TLS)】
クライアント ──→ サーバー証明書を要求
クライアント ←── サーバー証明書(CA署名済み)を提示
クライアントサーバー証明書を検証 ✅
クライアント ──→ クライアント証明書(CA署名済み)を提示
サーバー クライアント証明書を検証 ✅
↓
通信開始(双方の身元を確認済み)
証明書の信頼チェーン
Root CA(自己署名)
└── 中間CA(Root CAが署名)
├── サーバー証明書(中間CAが署名) ← API Gatewayが提示
└── クライアント証明書(中間CAが署名)← Salesforceが提示
どちらの証明書も同じCAチェーンに連なっていることをサーバー・クライアント双方が検証する。
TrustStore の役割
TrustStore とは、「信頼するCA証明書のリスト」を格納したファイルだ。API Gateway の mTLS 設定では、このTrustStore を S3 にアップロードし、そのURLを指定することで「このCAが署名したクライアント証明書のみを受け付ける」と設定できる。
truststore.pem の内容例:
-----BEGIN CERTIFICATE-----
(Root CA の公開鍵証明書)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
(中間CA の公開鍵証明書)
-----END CERTIFICATE-----
PEM形式で複数のCA証明書を連結して1ファイルにまとめる。
AWS での mTLS 対応サービス
| サービス | mTLS対応 | 設定方法 |
|---|---|---|
| API Gateway(HTTP API) | ✅ | カスタムドメイン + S3 TrustStore |
| API Gateway(REST API) | ✅ | カスタムドメイン + S3 TrustStore(TLS_1_2必須) |
| ALB(Application Load Balancer) | ✅ | リスナー設定 + ACM TrustStore |
| ACM(証明書管理) | ✅ | Private CA(ACM PCA)でクライアント証明書発行 |
注意: API Gateway の mTLS はカスタムドメインが必須だ。デフォルトの
execute-api.amazonaws.comエンドポイントでは mTLS を有効化できない。また、mTLS 強制のためにDisableExecuteApiEndpoint: trueの設定を忘れずに行う。
1-5. OAuth 2.0 Client Credentials フロー(M2M)
サービス間通信のための OAuth 2.0
OAuth 2.0 には複数のフローがあるが、サービス間通信(Machine-to-Machine: M2M) では Client Credentials フロー を使用する。ユーザーが介在しないため、ブラウザリダイレクトは不要だ。
| フロー | 用途 | ユーザー介在 |
|---|---|---|
| Authorization Code | Webアプリのユーザーログイン | あり |
| Implicit | SPA(非推奨) | あり |
| Client Credentials | M2M / サーバー間通信 | なし |
| Device Code | TV・IoT端末 | あり(別デバイス) |
Salesforce → AWS パターン
① Salesforce Apex
│ POST /oauth2/token
│ Authorization: Basic base64(client_id:client_secret)
│ Body: grant_type=client_credentials&scope=api/read
▼
② Cognito User Pool(トークンエンドポイント)
│ クライアントシークレット検証
│ スコープ検証
▼
③ access_token(JWT)を返却
│
├─ ヘッダー: { "alg": "RS256", "kid": "xxxx" }
├─ ペイロード: { "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/...",
│"client_id": "xxx", "scope": "api/read", "exp": 1234567890 }
└─ 署名: RS256(Cognitoの秘密鍵で署名)
④ Salesforce Apex
│ GET /api/resource
│ Authorization: Bearer <access_token>
│ (mTLSクライアント証明書も自動提示)
▼
⑤ API Gateway HTTP API
│ ① mTLS: クライアント証明書を TrustStore で検証 ✅
│ ② JWT Authorizer: access_token の署名・期限・スコープを検証 ✅
▼
⑥ Lambda(バックエンドAPI)
│ ビジネスロジック実行
▼
⑦ レスポンス返却
JWTの署名検証フロー(API GW Authorizer)
API Gateway の JWT Authorizer は以下の手順でトークンを検証する。
1. Authorization ヘッダーから Bearer トークンを取得
2. JWT ヘッダーから "kid"(Key ID)を取得
3. Cognito の JWKS エンドポイントから公開鍵を取得
→ https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
4. 公開鍵で JWT 署名を検証
5. exp(有効期限)・iss(発行者)・scope を検証
6. 検証成功 → Lambda を呼び出す
検証失敗 → 403 Forbidden を返す
ポイント: API Gateway は JWKS を自動でキャッシュする。Cognito の鍵ローテーション時には kid が変わるため、API Gateway は新しい kid を自動的に取得し直す。
1-6. 本記事のアーキテクチャ全体像
登場コンポーネント一覧
| コンポーネント | 役割 |
|---|---|
| Amazon Cognito User Pool | M2Mクライアント(App Client)管理・JWT発行 |
| Amazon Cognito Identity Pool | (参考)IAMロール付与(本記事では未使用) |
| Amazon API Gateway HTTP API | カスタムドメイン+mTLS+JWT Authorizer |
| AWS Lambda | バックエンドAPIロジック |
| AWS Certificate Manager (ACM) | サーバー証明書・クライアント証明書管理 |
| Amazon S3 | TrustStore(CA証明書PEM)格納 |
| Salesforce Apex | Named Credential + HTTPCallout(M2M呼び出し元) |
アーキテクチャ図
Salesforce Org
┌─────────────────────────────────────────┐
│ Apex Class │
│ ├── Named Credential(証明書設定済み)│
│ └── HTTPRequest(Bearer token付与) │
└────────────┬────────────────────────────┘
│ HTTPS + mTLS(クライアント証明書)
│ Authorization: Bearer <JWT>
▼
┌─────────────────────────────────────────┐
│ AWS Cloud│
│ │
│ ┌──────────────────────────────────┐│
│ │ API Gateway HTTP API ││
│ │ カスタムドメイン││
│ │ ① mTLS 検証(TrustStore@S3)││
│ │ ② JWT Authorizer(Cognito JWKS)││
│ └──────────┬───────────────────────┘│
│ │ 両検証 PASS のみ通過 │
│ ▼│
│ ┌──────────────────────────────────┐│
│ │ Lambda(バックエンドAPI)││
│ └──────────────────────────────────┘│
│ │
│ ┌────────────────┐ ┌───────────────┐ │
│ │ Cognito │ │ S3 Bucket │ │
│ │ User Pool│ │ truststore│ │
│ │ (JWT発行)│ │ .pem│ │
│ └────────────────┘ └───────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ ACM (Certificate Manager)│ │
│ │ ・サーバー証明書(API GW用) │ │
│ │ ・CA証明書(TrustStore用)│ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────┘
セキュリティ二重化の意義
| レイヤー | 検証内容 | 突破条件 |
|---|---|---|
| mTLS(L4) | クライアント証明書の有効性・CA信頼チェーン | 正規のクライアント証明書と秘密鍵が必要 |
| JWT Authorizer(L7) | トークンの署名・有効期限・スコープ | 有効な access_token が必要 |
| 二重保護の効果 | 証明書盗難だけではアクセス不可、トークン盗難だけでもアクセス不可 | 両方を同時に突破する必要がある |
証明書は長期的な信頼性(デバイス/サービスの認証)、JWTは短期的な認可(1時間単位のアクセス制御)を担う。この役割分担がエンタープライズセキュリティの基本パターンだ。
Section 1 まとめ
| 概念 | ポイント |
|---|---|
| Cognito User Pool | 認証(JWT発行)。M2Mでは App Client の Client Credentials フロー |
| Cognito Identity Pool | 認可(STS一時認証情報)。本記事では使用しない |
| mTLS | クライアント証明書による双方向認証。TrustStore を S3 に格納 |
| OAuth 2.0 Client Credentials | M2M用フロー。Salesforce が access_token を取得して API を呼ぶ |
| API GW JWT Authorizer | JWKS 経由で JWT 署名を自動検証。スコープ・有効期限も確認 |
| セキュリティ二重化 | mTLS(証明書)+ JWT(トークン)の二段階で API を完全保護 |
次のセクションでは、この概念を Terraform コードとして実装する。Cognito User Pool の作成・App Client 設定・Resource Server(カスタムスコープ)の定義から始めよう。
Sources:
– Amazon Cognito user pools – Amazon Cognito
– Amazon Cognito identity pools – Amazon Cognito
– How to turn on mutual TLS authentication for your HTTP APIs in API Gateway
– Configuring machine to machine Authentication with Amazon Cognito and Amazon API Gateway
Section 2: アーキテクチャ + AWSコンソール操作
2-1. 全体アーキテクチャ解説

本ハンズオンで構築するシステムは、Salesforce Apex から Amazon API Gateway(mTLS + JWT検証)を経由し、AWS Lambda で処理を行い、Amazon Cognito でアイデンティティ管理を行う構成です。
コンポーネントと役割
| コンポーネント | 役割 |
|---|---|
| Salesforce Apex | APIを呼び出すクライアント。mTLS用クライアント証明書を保持し、JWTをAuthorizationヘッダーに付与してリクエストを送信する |
| AWS ACM | クライアント証明書・サーバー証明書を発行・管理する認証局 |
| Amazon API Gateway | mTLS検証(TrustStoreによるクライアント証明書検証)+ JWT Authorizer(Cognito JWKSを参照してJWT署名検証)を担う。二重検証により高いセキュリティを実現 |
| Amazon S3 | CA TrustStore(trust-store.pem)を格納。API GatewayがmTLS検証時に参照する |
| AWS Lambda | ビジネスロジックを実行。Cognitoから取得したJWTのクレームを利用して認可判定を行う |
| Amazon Cognito User Pool | ユーザー認証基盤。JWTトークン(IdToken / AccessToken)を発行する |
| Amazon Cognito Identity Pool | CognitoユーザーにAWSリソースへのアクセス権限(一時認証情報)を付与する |
| AWS STS | Identity Poolからの要求に応じて、一時的なAWSアクセスキーを発行する |
データフロー
Salesforce Apex
│
│ ① mTLS (クライアント証明書 + TLS 1.2/1.3)
│ + Bearer JWT (Authorization ヘッダー)
▼
Amazon API Gateway
│ ├─ mTLS検証: S3 TrustStore (CA証明書) によるクライアント証明書検証
│ └─ JWT検証: Cognito JWKS エンドポイントによる署名検証
│
│ ② 検証成功 → リクエスト転送
▼
AWS Lambda
│ ├─ JWTクレーム (sub, email, custom:*) を使った認可判定
│ └─ ビジネスロジック実行
│
│ ③ (オプション) Identity Pool経由でAWS認証情報取得
▼
Amazon Cognito Identity Pool → AWS STS → 一時AWSアクセスキー
2-2. AWSコンソール: Cognito User Pool 作成
前提条件
- AWSアカウントが有効であること
- IAMユーザーに
cognito-idp:*権限があること(または Administrator Access) - リージョン:
ap-northeast-1(東京)を使用
手順
1. Cognitoコンソールへアクセス
AWSマネジメントコンソールにサインインし、検索バーで「Cognito」と入力して選択します。左ペインから 「ユーザープール」 を選択します。
2. ユーザープールの作成開始
「ユーザープールを作成」ボタンをクリックします。
[スクリーンショット: Cognitoコンソール トップ画面 — 「ユーザープールを作成」ボタン]
3. サインインオプションの設定
| 項目 | 設定値 |
|---|---|
| サインインオプション | Eメール にチェック |
| ユーザー名 | 任意(ハンズオンでは不要) |
[スクリーンショット: ステップ1 — 認証プロバイダーの設定画面]
4. セキュリティ要件の設定
| 項目 | 設定値 |
|---|---|
| パスワードポリシー | Cognito のデフォルト を選択 |
| 最小パスワード長 | 8文字 |
| 必須文字 | 大文字・小文字・数字 にチェック |
| MFA | なし(ハンズオン簡略化のため) |
| ユーザーアカウントの復元 | 自己登録を有効化 にチェック |
注意: 本番環境では MFA(SMS/TOTP)の有効化を強く推奨します。
5. サインアップエクスペリエンスの設定
| 項目 | 設定値 |
|---|---|
| 自己登録 | 有効化 |
| 属性の検証と確認 | Eメールメッセージを送信 |
| 検証属性 |
6. メッセージ配信の設定
| 項目 | 設定値 |
|---|---|
| Eメールプロバイダー | Cognitoで送信(ハンズオン用) |
本番環境では Amazon SES をカスタムドメインで設定することを推奨します(送信制限の緩和・ブランドメール送信のため)。
7. アプリの統合設定
| 項目 | 設定値 |
|---|---|
| ユーザープール名 | handson-user-pool |
| Cognito ドメイン | https://handson-auth-XXXX.auth.ap-northeast-1.amazoncognito.com(任意のプレフィックス) |
| アプリケーションタイプ | パブリッククライアント |
| アプリクライアント名 | handson-app-client |
| クライアントシークレット | クライアントシークレットを生成しない にチェック |
8. 認証フローの設定
「認証フロー」セクションで以下を有効化します:
- [x]
ALLOW_USER_PASSWORD_AUTH(ユーザー名+パスワード認証) - [x]
ALLOW_REFRESH_TOKEN_AUTH(リフレッシュトークン更新) - [ ]
ALLOW_USER_SRP_AUTH(SRP認証。ハンズオンでは無効化)
[スクリーンショット: アプリクライアント設定画面 — 認証フロー選択]
9. 確認と作成
設定内容を確認し、「ユーザープールを作成」をクリックします。
作成完了後、以下の値をメモしておきます:
ユーザープールID: ap-northeast-1_XXXXXXXXX
アプリクライアントID: XXXXXXXXXXXXXXXXXXXXXXXXXX
[スクリーンショット: ユーザープール作成完了画面 — PoolID・ClientID確認]
2-3. AWSコンソール: Cognito Identity Pool 作成
手順
1. フェデレーテッドアイデンティティへアクセス
Cognitoコンソール左ペインから 「フェデレーテッドアイデンティティ」 を選択します。
2024年以降のコンソールでは「Identity Pools」と表示される場合があります。
2. 新しいIDプールの作成
「新しいIDプールを作成」をクリックします。
| 項目 | 設定値 |
|---|---|
| IDプール名 | handson-identity-pool |
| 認証されていない ID へのアクセスを有効化 | チェックなし(セキュリティのため) |
3. 認証プロバイダーの設定
「認証プロバイダー」セクションで 「Cognito」 タブを選択し、以下を入力します:
| 項目 | 設定値 |
|---|---|
| ユーザープールID | ap-northeast-1_XXXXXXXXX(2-2でメモした値) |
| アプリクライアントID | XXXXXXXXXXXXXXXXXXXXXXXXXX(2-2でメモした値) |
[スクリーンショット: Identity Pool設定画面 — 認証プロバイダー(Cognito)入力フォーム]
4. IAMロールの設定
「プールの作成」をクリックすると、IAMロール設定画面が表示されます:
| ロール | デフォルト名 | 説明 |
|---|---|---|
| 認証済みロール | Cognito_handsonidentitypoolAuth_Role | 認証済みユーザーに付与されるAWS権限 |
| 未認証ロール | Cognito_handsonidentitypoolUnauth_Role | ゲストユーザー用(今回は不使用) |
認証済みロールの名前を変更します: handson-cognito-authenticated-role
「許可」 をクリックしてIDプールを作成します。
5. 作成完了の確認
IDプールID: ap-northeast-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
この値をメモしておきます(Section 4のLambda実装で使用)。
[スクリーンショット: Identity Pool作成完了画面 — IDプールID確認]
2-4. テストユーザー作成
AWS CLIによるテストユーザー登録
Section 2-2で作成したUser Poolに、テスト用ユーザーを追加します。
環境変数の設定
export USER_POOL_ID="ap-northeast-1_XXXXXXXXX"# 2-2でメモした値
export APP_CLIENT_ID="XXXXXXXXXXXXXXXXXXXXXXXXXX" # 2-2でメモした値
export AWS_REGION="ap-northeast-1"
ユーザー登録
# テストユーザーのサインアップ
aws cognito-idp sign-up \
--client-id ${APP_CLIENT_ID} \
--username testuser@example.com \
--password "TestPass123!" \
--user-attributes Name=email,Value=testuser@example.com \
--region ${AWS_REGION}
期待されるレスポンス:
{
"UserConfirmed": false,
"UserSub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
メール確認(管理者による強制確認)
ハンズオン環境ではメール受信の代わりに管理者確認コマンドを使用します:
# 管理者による強制確認(メール送信をスキップ)
aws cognito-idp admin-confirm-sign-up \
--user-pool-id ${USER_POOL_ID} \
--username testuser@example.com \
--region ${AWS_REGION}
本番環境での注意:
admin-confirm-sign-upはメール確認フローをバイパスします。本番では必ずメール確認フローを使用してください。
認証テスト(JWTトークン取得)
# 認証フローの実行(USER_PASSWORD_AUTH)
aws cognito-idp initiate-auth \
--auth-flow USER_PASSWORD_AUTH \
--auth-parameters \
USERNAME=testuser@example.com,PASSWORD="TestPass123!" \
--client-id ${APP_CLIENT_ID} \
--region ${AWS_REGION}
成功時のレスポンス(抜粋):
{
"AuthenticationResult": {
"AccessToken": "eyJraWQiOiJ...",
"ExpiresIn": 3600,
"TokenType": "Bearer",
"RefreshToken": "eyJjdHkiOiJ...",
"IdToken": "eyJraWQiOiJ..."
}
}
JWTトークンのデコード確認
# IdTokenのペイロード確認(デバッグ用)
aws cognito-idp initiate-auth \
--auth-flow USER_PASSWORD_AUTH \
--auth-parameters \
USERNAME=testuser@example.com,PASSWORD="TestPass123!" \
--client-id ${APP_CLIENT_ID} \
--region ${AWS_REGION} \
--query 'AuthenticationResult.IdToken' \
--output text | \
python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
payload = token.split('.')[1]
# Base64 パディング調整
payload += '=' * (4 - len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2, ensure_ascii=False))
"
期待されるペイロード(抜粋):
{
"sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"email_verified": true,
"iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX",
"cognito:username": "testuser@example.com",
"aud": "XXXXXXXXXXXXXXXXXXXXXXXXXX",
"token_use": "id",
"auth_time": 1744550000,
"exp": 1744553600,
"email": "testuser@example.com"
}
JWKS エンドポイントの確認
Cognito User Poolが公開しているJWKS(JSON Web Key Set)エンドポイントを確認します。このURLをAPI GatewayのJWT Authorizerに設定します(Section 3で使用):
# JWKS エンドポイントの確認
JWKS_URL="https://cognito-idp.${AWS_REGION}.amazonaws.com/${USER_POOL_ID}/.well-known/jwks.json"
echo "JWT Authorizer に設定するIssuer URL:"
echo "https://cognito-idp.${AWS_REGION}.amazonaws.com/${USER_POOL_ID}"
echo ""
echo "JWKS URL (API GWが自動参照):"
echo "${JWKS_URL}"
# JWKS の内容確認
curl -s "${JWKS_URL}" | python3 -m json.tool
2-5. Cognito認証フローの図解

上図はCognito User Poolを使ったJWT認証フローを示しています。
JWTトークンの種類と用途
| トークン | 有効期限 | 用途 |
|---|---|---|
| IdToken | 1時間(デフォルト) | ユーザー属性情報(email, sub等)を含む。API GW JWT Authorizerが検証 |
| AccessToken | 1時間(デフォルト) | Cognitoリソースへのアクセス権限スコープを含む |
| RefreshToken | 30日(デフォルト) | IdToken/AccessTokenの更新に使用。安全に保管すること |
セキュリティポイント: IdTokenは
audクレームでアプリクライアントIDが確認できます。API GWのJWT Authorizerはiss(Issuer)とaud(Audience)の両方を検証します。
2-6. mTLS TLSハンドシェイクの詳細

通常のTLS(片方向認証)と異なり、mTLSではサーバーがクライアントの証明書も検証します。これにより、有効なクライアント証明書を持つクライアントのみがAPIにアクセスできます。
mTLSとTLS(片方向)の比較
| 項目 | TLS(片方向) | mTLS(双方向) |
|---|---|---|
| サーバー証明書 | あり | あり |
| クライアント証明書 | なし | あり |
| 認証方向 | クライアント→サーバーを認証 | 相互認証 |
| API Gatewayの設定 | 不要 | TrustStore設定が必要 |
| セキュリティ強度 | 標準 | 高(証明書なしでは接続不可) |
API GatewayでのTrustStore設定
mTLSを有効化するには、S3にTrustStoreファイルを配置する必要があります:
# CA証明書(TrustStore)をS3にアップロード
BUCKET_NAME="handson-truststore-${AWS_REGION}-$(aws sts get-caller-identity --query Account --output text)"
# バケット作成
aws s3 mb s3://${BUCKET_NAME} --region ${AWS_REGION}
# TrustStoreファイルのアップロード(CA証明書を含むPEMファイル)
aws s3 cp trust-store.pem s3://${BUCKET_NAME}/trust-store.pem
echo "TrustStore URI: s3://${BUCKET_NAME}/trust-store.pem"
この s3:// URIをSection 3でAPI Gatewayのカスタムドメイン設定に使用します。
Section 2 まとめ
Section 2では以下を完了しました:
- [x] 全体アーキテクチャの理解(Salesforce Apex → API GW → Lambda → Cognito)
- [x] Cognito User Pool の作成(
handson-user-pool) - [x] アプリクライアントの設定(
handson-app-client、USER_PASSWORD_AUTH有効化) - [x] Cognito Identity Pool の作成(
handson-identity-pool) - [x] テストユーザーの作成と認証確認
- [x] JWTトークンの取得と内容確認
- [x] mTLS TLSハンドシェイクフローの理解
次のSection 3 では、API Gatewayのカスタムドメイン設定とmTLS TrustStoreの構成、JWT Authorizerの設定を行います。
Section 2 で取得した設定値(Section 3以降で使用)
USER_POOL_ID="ap-northeast-1_XXXXXXXXX"
APP_CLIENT_ID="XXXXXXXXXXXXXXXXXXXXXXXXXX"
IDENTITY_POOL_ID="ap-northeast-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
ISSUER_URL="https://cognito-idp.ap-northeast-1.amazonaws.com/${USER_POOL_ID}"
TRUSTSTORE_URI="s3://handson-truststore-ap-northeast-1-XXXXXXXXXXXX/trust-store.pem"
Section 3: mTLSハンズオン(Step A-B)
このセクションでは、Amazon CognitoとSalesforce Apexを結ぶmTLS(相互TLS認証)の基盤を、ゼロから手を動かして構築します。
| Step | 内容 | 使用サービス |
|---|---|---|
| A | mTLS証明書生成(CA証明書 + クライアント証明書) | OpenSSL |
| B-1 | S3 TrustStore設定 | Amazon S3 |
| B-2 | Lambda実行ロール作成 | IAM |
| B-3 | Lambda関数デプロイ | AWS Lambda |
| B-4 | API Gateway HTTP API設定(Cognito JWT Authorizer) | Amazon API Gateway |
| B-5 | 動作確認(mTLSなし版) | curl |
3-0. 前提条件確認
必要なツール
# OpenSSL インストール確認(3.x 推奨)
openssl version
# 出力例: OpenSSL 3.5.0 8 Apr 2025 (Library: OpenSSL 3.5.0 8 Apr 2025)
# AWS CLI バージョン確認(v2.x 必須)
aws --version
# 出力例: aws-cli/2.x.x Python/3.x.x ...
# AWS認証情報確認
aws sts get-caller-identity
OpenSSL 3.x について: OpenSSL 3.0以降、
genrsaの出力形式がPKCS#8形式(ヘッダー:BEGIN PRIVATE KEY)に変わりました。本ハンズオンでは内部的にCA署名を完結させるため動作に問題ありませんが、外部ツールで秘密鍵を読み込む際はバージョン差異に注意してください。
必要なIAM権限
以下のアクションが実行できるIAMユーザー/ロールを使用してください。
- S3:
s3:CreateBucket,s3:PutBucketVersioning,s3:PutObject - IAM:
iam:CreateRole,iam:AttachRolePolicy,iam:PassRole - Lambda:
lambda:CreateFunction,lambda:InvokeFunction - API Gateway:
apigateway:*
作業ディレクトリ構成
handson-cognito-mtls/
├── certs/ # 証明書ファイル
├── lambda/ # Lambda関数コード
└── terraform/ # Terraformコード(Section 4で使用)
# 作業ディレクトリ作成
mkdir -p handson-cognito-mtls/{certs,lambda,terraform}
cd handson-cognito-mtls/certs
Step A: mTLS証明書生成
mTLSでは、サーバー(API Gateway)がクライアント(Salesforce Apex)の証明書を検証します。そのために必要な証明書チェーンをOpenSSLで生成します。
CA秘密鍵 (ca.key)
↓ 自己署名
CA証明書 (ca.crt) ← TrustStoreとしてS3に登録
↓ 署名
クライアント証明書 (client.crt) ← Salesforceに設定
クライアント秘密鍵 (client.key)← Salesforceに設定
A-1. CA証明書生成
CA(認証局)証明書を生成します。API GatewayのTrustStoreに登録するルートCAです。
# Step A-1: CA秘密鍵とCA証明書生成
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key -out ca.crt \
-subj "/CN=handson-ca/O=Handson/C=JP"
コマンド解説:
| パラメータ | 値 | 説明 |
|---|---|---|
genrsa -out ca.key 4096 | 4096bit | CA秘密鍵。サーバー側CAとして4096bitを推奨 |
req -new -x509 | — | 自己署名証明書を直接生成(CSR不要) |
-days 365 | 365日 | 本番環境では3年以上を推奨 |
-subj "/CN=handson-ca/O=Handson/C=JP" | — | 証明書の識別情報 |
本番環境の注意点:
-days 365は学習用の設定です。本番では証明書有効期限の管理が重要で、期限切れはAPI呼び出しの全断を招きます。証明書更新のアラート設定と、S3バージョニングによるロールバック手順(B-1で設定)を必ず準備してください。
A-2. クライアント証明書生成
Salesforce Apexが送信するクライアント証明書を生成します。
# Step A-2: クライアント秘密鍵とCSR生成
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr \
-subj "/CN=salesforce-client/O=Handson/C=JP"
CSR(Certificate Signing Request)とは:
CSRはCAに「この公開鍵に対して証明書を発行してください」と依頼するファイルです。CA署名後は使用済みになるため、保管は不要です。
| ファイル | 用途 | 保管 |
|---|---|---|
client.key | クライアント秘密鍵 | Salesforceに設定(厳重管理) |
client.csr | 証明書署名要求 | CA署名後は不要 |
A-3. CAによるクライアント証明書署名
CAがクライアント証明書に署名することで、信頼チェーンを確立します。
# Step A-3: CAによるクライアント証明書署名
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out client.crt
信頼チェーンの仕組み:
API Gateway TrustStore (ca.crt 登録済み)
↓ 「このCAが署名した証明書を信頼する」
Salesforceが送信する client.crt
↓ 「ca.crt で署名されている → 信頼できる」
mTLS ハンドシェイク成功 → リクエスト通過
A-4. 証明書ファイル一覧確認
# Step A-4: 証明書確認
openssl x509 -in client.crt -text -noout | grep -E "Subject|Issuer|Not"
出力例:
Issuer: CN=handson-ca, O=Handson, C=JP
Validity
Not Before: Apr 13 07:00:00 2026 GMT
Not After : Apr 13 07:00:00 2027 GMT
Subject: CN=salesforce-client, O=Handson, C=JP
確認ポイント:
– Issuer が CN=handson-ca(A-1で生成したCA)であること
– Subject が CN=salesforce-client(A-2で設定したCN)であること
– Not After の有効期限を必ずメモしておくこと
# 証明書ファイル一覧確認
ls -la certs/
# ca.key (CA秘密鍵 — 厳重管理必須、外部に出さない)
# ca.crt (CA証明書 — TrustStoreとしてS3に保存)
# ca.srl (シリアル番号ファイル)
# client.key (クライアント秘密鍵 — Salesforceに設定)
# client.csr (CSR — 使用済み、保管不要)
# client.crt (クライアント証明書 — Salesforceに設定)
セキュリティ注意:
ca.key(CA秘密鍵)が漏洩すると、攻撃者が任意のクライアント証明書を発行してAPIに不正アクセスできます。本番環境ではAWS Secrets ManagerやAWS KMSでの管理を検討してください。
Step B: AWS環境構築
mTLS証明書の準備ができたら、AWS側のコンポーネントを順に構築します。
B-1. S3 TrustStore設定
CA証明書(ca.crt)をS3にアップロードし、API GatewayのTrustStoreとして使用します。
# S3バケット作成(TrustStore格納用)
aws s3api create-bucket \
--bucket handson-mtls-truststore-YOURNAME \
--region ap-northeast-1 \
--create-bucket-configuration LocationConstraint=ap-northeast-1
# バージョニング有効化(TrustStore更新を追跡)
aws s3api put-bucket-versioning \
--bucket handson-mtls-truststore-YOURNAME \
--versioning-configuration Status=Enabled
# CA証明書をTrustStoreとしてアップロード
aws s3 cp ca.crt s3://handson-mtls-truststore-YOURNAME/truststore.pem
YOURNAME の置き換え: S3バケット名はグローバル一意です。handson-mtls-truststore-yamadaのように自分の名前やIDを付けてください。
B-2. Lambda実行ロール作成
Lambda関数がCloudWatch Logsに書き込むためのIAMロールを作成します。
# Lambda実行ロール用Trust Policy
cat > lambda-trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
EOF
aws iam create-role \
--role-name handson-lambda-exec-role \
--assume-role-policy-document file://lambda-trust-policy.json
aws iam attach-role-policy \
--role-name handson-lambda-exec-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
実行後、ARNをメモしてください(B-3で使用):
# ロールARN確認
aws iam get-role --role-name handson-lambda-exec-role \
--query 'Role.Arn' --output text
# 出力例: arn:aws:iam::123456789012:role/handson-lambda-exec-role
B-3. Lambda関数デプロイ
Cognito JWTクレームからユーザー情報を返すLambda関数を作成します。
まず関数コード index.js を作成します:
// lambda/index.js
exports.handler = async (event) => {
const claims = event.requestContext.authorizer?.jwt?.claims || {};
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: 'Hello from mTLS-secured API',
user: claims.email || 'unknown',
sub: claims.sub || 'unknown',
})
};
};
コードのポイント:
event.requestContext.authorizer.jwt.claims: API GatewayのJWT Authorizerが検証済みトークンのクレームをここに注入しますclaims.email: CognitoユーザープールのIDトークンに含まれるメールアドレスclaims.sub: CognitoユーザーのサブジェクトID(一意識別子)
# Lambda関数作成
zip lambda.zip index.js
aws lambda create-function \
--function-name handson-cognito-api \
--runtime nodejs20.x \
--role arn:aws:iam::<ACCOUNT_ID>:role/handson-lambda-exec-role \
--handler index.handler \
--zip-file fileb://lambda.zip \
--region ap-northeast-1
Lambda runtime について:
nodejs20.xは2026年4月30日にEOLとなります。本番環境ではnodejs22.xまたはnodejs24.xへの移行を推奨します。
# アカウントIDの確認
aws sts get-caller-identity --query 'Account' --output text
[スクリーンショット: Lambda作成完了画面]B-4. API Gateway HTTP API設定
HTTP API vs REST APIの選択理由:
| 特性 | HTTP API | REST API |
|---|---|---|
| コスト | $1.00/百万リクエスト | $3.50/百万リクエスト |
| Cognito JWT認証 | ネイティブ対応(組み込み) | Lambdaオーソライザー必要 |
| 設定の複雑さ | シンプル | 複雑 |
| mTLS対応 | カスタムドメイン経由で対応 | カスタムドメイン経由で対応 |
本ハンズオンではCognito JWT認証のネイティブサポートと低コストを理由にHTTP APIを採用します。
# HTTP API作成
aws apigatewayv2 create-api \
--name handson-cognito-api \
--protocol-type HTTP \
--region ap-northeast-1
出力からAPI IDをメモします(以降 <API_ID> として使用):
{
"ApiId": "abc1234xyz",
"ApiEndpoint": "https://abc1234xyz.execute-api.ap-northeast-1.amazonaws.com",
...
}
# Cognito JWT Authorizer設定
aws apigatewayv2 create-authorizer \
--api-id <API_ID> \
--authorizer-type JWT \
--identity-source '$request.header.Authorization' \
--name cognito-authorizer \
--jwt-configuration \
Audience=<APP_CLIENT_ID>,\
Issuer=https://cognito-idp.ap-northeast-1.amazonaws.com/<USER_POOL_ID> \
--region ap-northeast-1
# Lambda統合設定
aws apigatewayv2 create-integration \
--api-id <API_ID> \
--integration-type AWS_PROXY \
--integration-uri arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:handson-cognito-api \
--payload-format-version 2.0 \
--region ap-northeast-1
# ルート設定(JWT Authorizer付き)
aws apigatewayv2 create-route \
--api-id <API_ID> \
--route-key 'GET /users/me' \
--authorization-type JWT \
--authorizer-id <AUTHORIZER_ID> \
--target integrations/<INTEGRATION_ID> \
--region ap-northeast-1
# デプロイ
aws apigatewayv2 create-stage \
--api-id <API_ID> \
--stage-name prod \
--auto-deploy \
--region ap-northeast-1
[スクリーンショット: API Gateway設定画面]Lambdaリソースベースポリシーの追加:
aws lambda add-permission \
--function-name handson-cognito-api \
--statement-id allow-apigw-invoke \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:ap-northeast-1:<ACCOUNT_ID>:<API_ID>users/me" \
--region ap-northeast-1
B-5. 動作確認(mTLSなし版)
まずCognitoからIDトークンを取得します:
# Cognitoユーザーでサインイン(USER_PASSWORD_AUTH フロー)
aws cognito-idp initiate-auth \
--auth-flow USER_PASSWORD_AUTH \
--client-id <APP_CLIENT_ID> \
--auth-parameters USERNAME=testuser@example.com,PASSWORD=TestPass1! \
--region ap-northeast-1
# → IdToken を取得してメモ
取得したIDトークンでAPIを呼び出します:
# カスタムドメインなしでの動作確認(mTLSなし版)
curl -X GET \
https://<API_ID>.execute-api.ap-northeast-1.amazonaws.com/prod/users/me \
-H "Authorization: Bearer <ID_TOKEN>"
# 期待レスポンス: {"message":"Hello from mTLS-secured API","user":"testuser@example.com",...}
期待レスポンス例:
{
"message": "Hello from mTLS-secured API",
"user": "testuser@example.com",
"sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
トラブルシューティング:
| エラー | 原因 | 対処 |
|---|---|---|
{"message":"Unauthorized"} | トークン期限切れ or Audienceミスマッチ | IDトークンを再取得 / APP_CLIENT_IDを確認 |
{"message":"Internal Server Error"} | Lambda実行エラー | CloudWatch Logsを確認 |
curl: (6) Could not resolve host | API_IDまたはリージョンのミス | エンドポイントURLを再確認 |
Section 3 まとめ
このセクションで構築したコンポーネント:
| コンポーネント | リソース名 | 用途 |
|---|---|---|
| S3バケット | handson-mtls-truststore-YOURNAME | CA証明書(TrustStore)格納 |
| IAMロール | handson-lambda-exec-role | Lambda実行ロール |
| Lambda関数 | handson-cognito-api | JWTクレーム確認API |
| HTTP API | handson-cognito-api | API Gatewayエンドポイント |
| JWT Authorizer | cognito-authorizer | Cognito JWT検証 |
次のSection 4では:
- ACM証明書の取得とカスタムドメイン設定
- API GatewayのmTLS有効化(
truststore.pemの紐付け) - Terraformで上記全構成をコード化
これにより、Salesforce ApexがJWT + クライアント証明書の両方を提示する真のmTLS構成が完成します。
Section 4: Terraform IaC編
Section 2・3でAWSコンソールおよびCLIを使って構築した構成を、Terraformで完全に再現します。Infrastructure as Code(IaC)により、環境の再現性・バージョン管理・チームでの共有が容易になります。
4-1. Terraform ディレクトリ構成
terraform/
├── main.tf# 全リソース定義(Cognito、Lambda、API GW、IAM、S3)
├── variables.tf # 変数定義
├── outputs.tf# 出力値(デプロイ後に参照するID等)
└── terraform.tfvars# 変数値(git管理外。.gitignoreに追加推奨)
補助ファイル(terraformディレクトリの外):
project-root/
├── certs/
│└── ca.crt # Section 3で作成したCA証明書(mTLS用truststore)
├── lambda/
│└── lambda.zip# Lambda関数のzipパッケージ
└── terraform/ # ← Terraformコード一式
ポイント:
terraform.tfvarsは個人の環境情報(バケット名サフィックス等)を含むため、.gitignoreに追加してGit管理外にしておくことを強く推奨します。
4-2. main.tf 解説
main.tf に全AWSリソースを定義します。以下のコードを 一字一句そのまま 使用してください。
terraform { required_version = ">= 1.7.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = "ap-northeast-1" } # Cognito User Pool resource "aws_cognito_user_pool" "handson" { name = "handson-user-pool" password_policy { minimum_length = 8 require_uppercase = true require_lowercase = true require_numbers= true require_symbols= false } auto_verified_attributes = ["email"] schema { attribute_data_type = "String" name = "email" required= true mutable = true } tags = { Name = "handson-user-pool" Environment = "dev" } } # Cognito User Pool Client resource "aws_cognito_user_pool_client" "handson" { name= "handson-app-client" user_pool_id = aws_cognito_user_pool.handson.id explicit_auth_flows = [ "ALLOW_USER_PASSWORD_AUTH", "ALLOW_REFRESH_TOKEN_AUTH", ] generate_secret = false } # Cognito Identity Pool resource "aws_cognito_identity_pool" "handson" { identity_pool_name= "handson-identity-pool" allow_unauthenticated_identities = false cognito_identity_providers { client_id= aws_cognito_user_pool_client.handson.id provider_name = aws_cognito_user_pool.handson.endpoint server_side_token_check = false } tags = { Name = "handson-identity-pool" Environment = "dev" } } # IAM Role for authenticated users (Identity Pool) resource "aws_iam_role" "cognito_authenticated" { name = "handson-cognito-authenticated-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Federated = "cognito-identity.amazonaws.com" } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "cognito-identity.amazonaws.com:aud" = aws_cognito_identity_pool.handson.id } "ForAnyValue:StringLike" = { "cognito-identity.amazonaws.com:amr" = "authenticated" } } }] }) } resource "aws_iam_role_policy" "cognito_authenticated" { name = "handson-cognito-authenticated-policy" role = aws_iam_role.cognito_authenticated.id policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Action = ["execute-api:Invoke"] Resource = "${aws_apigatewayv2_api.handson.execution_arn}/prod/GET/users/me" }] }) } resource "aws_cognito_identity_pool_roles_attachment" "handson" { identity_pool_id = aws_cognito_identity_pool.handson.id roles = { "authenticated" = aws_iam_role.cognito_authenticated.arn } } # S3 bucket for mTLS truststore resource "aws_s3_bucket" "truststore" { bucket = "handson-mtls-truststore-${var.suffix}" tags = { Name = "handson-mtls-truststore" Environment = "dev" } } resource "aws_s3_bucket_versioning" "truststore" { bucket = aws_s3_bucket.truststore.id versioning_configuration { status = "Enabled" } } resource "aws_s3_object" "truststore" { bucket = aws_s3_bucket.truststore.id key = "truststore.pem" source = var.truststore_pem_path etag= filemd5(var.truststore_pem_path) } # Lambda execution role resource "aws_iam_role" "lambda_exec" { name = "handson-lambda-exec-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" "lambda_basic" { role = aws_iam_role.lambda_exec.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } # Lambda function resource "aws_lambda_function" "handson" { filename= var.lambda_zip_path function_name = "handson-cognito-api" role = aws_iam_role.lambda_exec.arn handler = "index.handler" runtime = "nodejs20.x" source_code_hash = filebase64sha256(var.lambda_zip_path) tags = { Name = "handson-cognito-api" Environment = "dev" } } # API Gateway HTTP API resource "aws_apigatewayv2_api" "handson" { name = "handson-cognito-api" protocol_type = "HTTP" tags = { Name = "handson-cognito-api" Environment = "dev" } } # JWT Authorizer (Cognito) resource "aws_apigatewayv2_authorizer" "cognito" { api_id = aws_apigatewayv2_api.handson.id authorizer_type = "JWT" identity_sources = ["$request.header.Authorization"] name = "cognito-authorizer" jwt_configuration { audience = [aws_cognito_user_pool_client.handson.id] issuer= "https://cognito-idp.ap-northeast-1.amazonaws.com/${aws_cognito_user_pool.handson.id}" } } # Lambda integration resource "aws_apigatewayv2_integration" "lambda" { api_id = aws_apigatewayv2_api.handson.id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.handson.invoke_arn payload_format_version = "2.0" } # Route with JWT auth resource "aws_apigatewayv2_route" "get_users_me" { api_id = aws_apigatewayv2_api.handson.id route_key = "GET /users/me" authorization_type = "JWT" authorizer_id= aws_apigatewayv2_authorizer.cognito.id target = "integrations/${aws_apigatewayv2_integration.lambda.id}" } # Stage resource "aws_apigatewayv2_stage" "prod" { api_id= aws_apigatewayv2_api.handson.id name = "prod" auto_deploy = true tags = { Name = "handson-api-prod" Environment = "dev" } } # Lambda permission for API GW resource "aws_lambda_permission" "apigw" { statement_id = "AllowAPIGatewayInvoke" action = "lambda:InvokeFunction" function_name = aws_lambda_function.handson.function_name principal = "apigateway.amazonaws.com" source_arn = "${aws_apigatewayv2_api.handson.execution_arn}*"—/*/*で全ステージ・全メソッドを許可。ステージ追加時の都度更新が不要になる。4-3. variables.tf 解説
variable "suffix" { description = "リソース名の一意サフィックス(例: yourname)" type = string } variable "truststore_pem_path" { description = "CA証明書ファイルパス(ローカル)" type = string default = "certs/ca.crt" } variable "lambda_zip_path" { description = "Lambda関数zipファイルパス" type = string default = "lambda/lambda.zip" }
| 変数名 | 必須/任意 | 用途 |
|---|---|---|
suffix | 必須(defaultなし) | S3バケット名のグローバル一意性確保。自分の名前や乱数を入れる |
truststore_pem_path | 任意 | Section 3で作成した ca.crt のパス |
lambda_zip_path | 任意 | Lambda関数のzipファイルパス |
4-4. outputs.tf 解説
output "user_pool_id" {
description = "Cognito User Pool ID"
value = aws_cognito_user_pool.handson.id
}
output "user_pool_client_id" {
description = "Cognito App Client ID"
value = aws_cognito_user_pool_client.handson.id
}
output "identity_pool_id" {
description = "Cognito Identity Pool ID"
value = aws_cognito_identity_pool.handson.id
}
output "api_endpoint" {
description = "API Gateway エンドポイントURL"
value = "${aws_apigatewayv2_stage.prod.invoke_url}/users/me"
}
output "truststore_s3_uri" {
description = "TrustStore S3 URI"
value = "s3://${aws_s3_bucket.truststore.id}/truststore.pem"
}
terraform apply 完了後に terraform output で全出力値を確認できます。Section 5のSalesforce設定で必要となるため、メモしておいてください。
4-5. terraform.tfvars 作成
suffix = "YOURNAME"
truststore_pem_path = "certs/ca.crt"
lambda_zip_path = "lambda/lambda.zip"
.gitignore への追加:
echo "terraform/terraform.tfvars" >> .gitignore
echo "terraform/.terraform/" >> .gitignore
echo "terraform/*.tfstate" >> .gitignore
echo "terraform/*.tfstate.backup" >> .gitignore
4-6. Terraform 実行手順
事前準備
# 必要ファイルの存在確認
ls certs/ca.crt
ls lambda/lambda.zip
lambda.zip は事前に以下のように作成しておきます:
mkdir -p lambda
cat > lambda/index.js << 'EOF'
exports.handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify({
message: "Hello from handson Lambda",
userId: event.requestContext?.authorizer?.jwt?.claims?.sub
})
};
};
EOF
cd lambda && zip lambda.zip index.js && cd ..
Terraform 実行
cd terraform/
# 初期化(プロバイダーのダウンロード)
terraform init
# プラン確認(実際の変更内容を事前確認)
terraform plan
# 適用(確認プロンプトで "yes" を入力)
terraform apply
# Enter a value: yes
# 出力値の確認
terraform output
実行完了後の出力例:
api_endpoint = "https://abc123xyz.execute-api.ap-northeast-1.amazonaws.com/prod/users/me"
identity_pool_id = "ap-northeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
truststore_s3_uri= "s3://handson-mtls-truststore-yourname/truststore.pem"
user_pool_client_id = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
user_pool_id = "ap-northeast-1_XXXXXXXXX"
4-7. mTLS カスタムドメイン設定の補足
API Gateway の mTLS はカスタムドメインを設定した場合のみサポートされています。デフォルトエンドポイントでは動作しません。
カスタムドメインがある場合は、以下のリソースを main.tf に追加します:
# ACM証明書(事前に取得済みのARNを使用)
resource "aws_apigatewayv2_domain_name" "handson" {
domain_name = "api.example.com" # 自分のドメインに変更
domain_name_configuration {
certificate_arn = var.acm_certificate_arn
endpoint_type= "REGIONAL"
security_policy = "TLS_1_2"
}
mutual_tls_authentication {
truststore_uri = "s3://${aws_s3_bucket.truststore.id}/truststore.pem"
truststore_version = aws_s3_object.truststore.version_id
}
}
resource "aws_apigatewayv2_api_mapping" "handson" {
api_id= aws_apigatewayv2_api.handson.id
domain_name = aws_apigatewayv2_domain_name.handson.id
stage = aws_apigatewayv2_stage.prod.id
}
独自ドメインがない場合の動作確認
# Step 1: テストユーザー作成
aws cognito-idp admin-create-user \
--user-pool-id $(terraform output -raw user_pool_id) \
--username testuser@example.com \
--temporary-password "TempPass123!" \
--message-action SUPPRESS \
--region ap-northeast-1
# Step 2: パスワードを CONFIRMED 状態に変更
aws cognito-idp admin-set-user-password \
--user-pool-id $(terraform output -raw user_pool_id) \
--username testuser@example.com \
--password "TestPass123" \
--permanent \
--region ap-northeast-1
# Step 3: JWT トークン取得
TOKEN=$(aws cognito-idp initiate-auth \
--auth-flow USER_PASSWORD_AUTH \
--auth-parameters USERNAME=testuser@example.com,PASSWORD=TestPass123 \
--client-id $(terraform output -raw user_pool_client_id) \
--region ap-northeast-1 \
--query 'AuthenticationResult.IdToken' --output text)
# Step 4: API 呼び出し
curl -X GET \
$(terraform output -raw api_endpoint) \
-H "Authorization: Bearer $TOKEN"
Section 4 まとめ
| 作成リソース | Terraformリソース |
|---|---|
| Cognito User Pool | aws_cognito_user_pool |
| Cognito App Client | aws_cognito_user_pool_client |
| Cognito Identity Pool | aws_cognito_identity_pool |
| IAM Role(認証済みユーザー) | aws_iam_role + aws_iam_role_policy |
| S3(mTLS TrustStore) | aws_s3_bucket + aws_s3_object |
| Lambda関数 | aws_lambda_function |
| API Gateway HTTP API | aws_apigatewayv2_api |
| JWT Authorizer | aws_apigatewayv2_authorizer |
| ルート・統合・ステージ | aws_apigatewayv2_route / _integration / _stage |
次のSection 5では、TerraformのOutputで取得した user_pool_id と user_pool_client_id を使い、Salesforce側のConnected App設定とApexコードの実装に進みます。
Section 5: Salesforce Apex から Cognito 保護 API を呼び出す
ここまでのセクションで、Terraform による Cognito User Pool・Lambda・API Gateway(mTLS + JWT Authorizer)の構築が完了した。このセクションでは Salesforce Apex から実際に API を呼び出す実装を行う。エンタープライズシステムの定番プラットフォームである Salesforce から AWS バックエンドへの安全な連携パターンを習得しよう。
5-1. Salesforce 側の事前準備
動作環境
本ハンズオンは Salesforce Developer Edition または Sandbox 環境 を使用する。
注意: Salesforce の無料 Developer Edition は developer.salesforce.com から取得できる。
Remote Site Settings の追加(必須)
Salesforce からの外部 HTTP コールアウトは、事前に許可リストへの登録が必要。API Gateway のエンドポイントを登録する。
[操作手順]
1. Salesforce Setup を開く(右上の歯車アイコン → 「Setup」)
2. Quick Find に「Remote Site Settings」と入力
3. 「New Remote Site」をクリック
4. 以下を入力して保存:
- Remote Site Name: AWS_Cognito_API
- Remote Site URL: https://<API_ID>.execute-api.ap-northeast-1.amazonaws.com
- Active: ✓(チェック済み)
<API_ID> は Terraform の出力値 api_endpoint から取得する(Section 4 参照)。
Custom Label の設定(認証情報の安全な管理)
Apex コード内に認証情報をハードコードするのは 厳禁。Custom Label を使って認証情報を外部化する。
[操作手順]
1. Setup → Quick Find → 「Custom Labels」
2. 「New Custom Label」で以下の3つを作成:
ラベル名: CognitoUsername
値: testuser@example.com
ラベル名: CognitoPassword
値: TestPass123
※本番環境では AWS Secrets Manager 連携を強く推奨(後述)
ラベル名: CognitoClientId
値: <APP_CLIENT_ID>(Terraform 出力の user_pool_client_id)
本番環境の推奨: パスワードのような機密情報は Custom Label ではなく、AWS Secrets Manager に格納し、Lambda 経由で取得するアーキテクチャが望ましい。
5-2. Named Credential の設定
Named Credential は Salesforce から外部エンドポイントへのコールアウトを管理する機能。
2026年時点の注記: Salesforce は Legacy Named Credential を非推奨とし、External Credential + Secured Endpoint(新方式)への移行を推進している。本ハンズオンでは学習目的で Legacy タイプを使用するが、本番環境では新方式の採用を検討すること。
メタデータ API 経由での設定(SFDX 推奨)
<!-- ファイル: force-app/main/default/namedCredentials/AWS_Cognito_API.namedCredential-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
<allowMergeFieldsInBody>false</allowMergeFieldsInBody>
<allowMergeFieldsInHeader>false</allowMergeFieldsInHeader>
<calloutStatus>Enabled</calloutStatus>
<generateAuthorizationHeader>false</generateAuthorizationHeader>
<label>AWS Cognito API</label>
<name>AWS_Cognito_API</name>
<principalType>Anonymous</principalType>
<protocol>NoAuthentication</protocol>
<type>Legacy</type>
<url>https://api.example.com/prod</url>
</NamedCredential>
<url> を Terraform 出力の api_endpoint ベース URL に置換して保存する。protocol: NoAuthentication を指定している理由は、JWT トークンの付与は Apex コード側で行うため。
GUI での設定(代替手順)
Setup → Quick Find → 「Named Credentials」→「New Legacy」
- Label: AWS Cognito API
- Name: AWS_Cognito_API
- URL: https://<API_ID>.execute-api.ap-northeast-1.amazonaws.com/prod
- Identity Type: Anonymous
- Authentication Protocol: No Authentication
[スクリーンショット: Named Credential 設定画面 — URL と NoAuthentication の確認]5-3. CognitoAuthService 実装(JWT トークン取得)
Cognito の USER_PASSWORD_AUTH フローを使い、ユーザー名・パスワードから JWT トークンを取得する Apex クラスを実装する。
public class CognitoAuthService {
private static final String TOKEN_ENDPOINT =
'https://cognito-idp.ap-northeast-1.amazonaws.com/<USER_POOL_ID>/oauth2/token';
public static String getAccessToken(String username, String password, String clientId) {
HttpRequest req = new HttpRequest();
req.setEndpoint(TOKEN_ENDPOINT);
req.setMethod('POST');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setBody(
'grant_type=password' +
'&client_id=' + EncodingUtil.urlEncode(clientId, 'UTF-8') +
'&username=' + EncodingUtil.urlEncode(username, 'UTF-8') +
'&password=' + EncodingUtil.urlEncode(password, 'UTF-8') +
'&scope=openid+email'
);
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() != 200) {
throw new CognitoAuthException(
'Cognito authentication failed: ' + res.getStatus() +
' - ' + res.getBody()
);
}
Map<String, Object> tokenResponse =
(Map<String, Object>) JSON.deserializeUntyped(res.getBody());
return (String) tokenResponse.get('id_token');
}
public class CognitoAuthException extends Exception {}
}
TOKEN_ENDPOINT の <USER_POOL_ID> を実際の User Pool ID に置換すること(Terraform 出力の user_pool_id から取得)。
| ポイント | 説明 |
|---|---|
grant_type=password | Cognito の USER_PASSWORD_AUTH フローに対応 |
EncodingUtil.urlEncode | ユーザー名・パスワードに特殊文字が含まれる場合のエスケープ処理 |
id_token の取得 | API Gateway の JWT Authorizer が検証するトークン |
| カスタム例外クラス | CognitoAuthException で認証エラーを明示的にハンドリング |
Cognito エンドポイントへの Callout:
cognito-idp.ap-northeast-1.amazonaws.comも Remote Site Settings への登録が必要。
5-4. CognitoApiService 実装(API コールアウト)
認証トークンを使って、API Gateway 経由で Lambda に HTTP リクエストを送る Apex クラスを実装する。
public class CognitoApiService {
private static final String NAMED_CRED = 'callout:AWS_Cognito_API';
public static UserInfoResponse getUserInfo() {
String idToken = CognitoAuthService.getAccessToken(
Label.CognitoUsername,
Label.CognitoPassword,
Label.CognitoClientId
);
HttpRequest req = new HttpRequest();
req.setEndpoint(NAMED_CRED + '/users/me');
req.setMethod('GET');
req.setHeader('Content-Type', 'application/json');
req.setHeader('Authorization', 'Bearer ' + idToken);
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() != 200) {
throw new CalloutException(
'API call failed: ' + res.getStatus() + ' - ' + res.getBody()
);
}
return (UserInfoResponse) JSON.deserialize(res.getBody(), UserInfoResponse.class);
}
public class UserInfoResponse {
public String message;
public String user;
public String sub;
}
}
| ポイント | 説明 |
|---|---|
callout:AWS_Cognito_API | Named Credential を参照するプレフィックス構文 |
NAMED_CRED + '/users/me' | Named Credential の URL + パスを結合してエンドポイントを指定 |
Authorization: Bearer {idToken} | API Gateway の JWT Authorizer が検証するヘッダー |
Label.CognitoUsername | Custom Label から認証情報を取得(ハードコード回避) |
Lambda のレスポンスは以下の形式:
{
"message": "Hello from mTLS-secured API",
"user": "testuser@example.com",
"sub": "abc-123-def-456"
}
5-5. テストクラスの実装
Salesforce では外部 HTTP コールアウトを含むコードに対し、HttpCalloutMock を使ったユニットテストが必要(コードカバレッジ 75% 以上が本番デプロイの要件)。
@isTest
public class CognitoApiServiceTest {
@isTest
static void testGetUserInfo_Success() {
Test.setMock(HttpCalloutMock.class, new CognitoApiMock(200,
'{"message":"Hello from mTLS-secured API","user":"test@example.com","sub":"abc-123"}'
));
Test.startTest();
CognitoApiService.UserInfoResponse result = CognitoApiService.getUserInfo();
Test.stopTest();
System.assertEquals('test@example.com', result.user);
System.assertEquals('abc-123', result.sub);
}
@isTest
static void testGetUserInfo_Unauthorized() {
Test.setMock(HttpCalloutMock.class, new CognitoApiMock(401, '{"message":"Unauthorized"}'));
Test.startTest();
try {
CognitoApiService.getUserInfo();
System.assert(false, 'Exception expected');
} catch (CalloutException e) {
System.assert(e.getMessage().contains('API call failed'));
}
Test.stopTest();
}
private class CognitoApiMock implements HttpCalloutMock {
private Integer statusCode;
private String body;
CognitoApiMock(Integer code, String body) {
this.statusCode = code;
this.body = body;
}
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatusCode(statusCode);
res.setBody(body);
return res;
}
}
}
テストの実行
SFDX CLI からテストを実行:
sfdx force:apex:test:run --testname CognitoApiServiceTest --result-format human
5-6. 動作確認(Anonymous Apex)
テストが通ったら、実際の Cognito + API Gateway に対して動作確認を行う。Developer Console の Anonymous Apex で以下を実行する。
CognitoApiService.UserInfoResponse result = CognitoApiService.getUserInfo();
System.debug('User: ' + result.user);
System.debug('Sub: ' + result.sub);
System.debug('Message: ' + result.message);
実行手順
1. Salesforce Setup → 「Developer Console」を開く
2. Debug → 「Open Execute Anonymous Window」(または Ctrl+E)
3. 上記コードを貼り付けて「Execute」
4. Debug Log の「USER_DEBUG」行を確認
[スクリーンショット: Developer Console Debug Log — User/Sub/Message の出力を確認]期待する出力例:
USER_DEBUG [3]|DEBUG|User: testuser@example.com
USER_DEBUG [4]|DEBUG|Sub: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
USER_DEBUG [5]|DEBUG|Message: Hello from mTLS-secured API
Section 6: セキュリティTips + トラブルシューティング
本構成では Cognito・mTLS・Named Credential と複数のセキュリティレイヤーが絡み合う。各レイヤーの注意点と、よくある問題の解決策を整理する。
6-1. セキュリティベストプラクティス
| リスク | 対策 |
|---|---|
| 認証情報のハードコード | Custom Label / Named Credential の使用(本ハンズオン実装済み) |
| CA秘密鍵の漏洩 | ca.key を Git 管理外に置く(.gitignore に追加)・HSM 格納(本番) |
| 証明書の期限切れ | ACM Private CA の自動更新を設定 / CloudWatch アラームで期限監視 |
| JWT の長期有効 | access_token の有効期限を短く設定(推奨: 1時間)・refresh_token で再取得 |
| mTLS バイパス | API Gateway TrustStore の Versioning で証明書更新を追跡 |
| Salesforce 認証情報の漏洩 | Custom Label を避け AWS Secrets Manager に格納・Lambda 経由で動的取得 |
| TrustStore の未更新 | 証明書失効時に古い TrustStore が残ると攻撃者の通過を許す可能性がある |
CA 秘密鍵の Git 除外設定
# .gitignore に追加
echo "certs/ca.key" >> .gitignore
echo "certs/client.key" >> .gitignore
echo "certs/*.csr" >> .gitignore
# すでにコミット済みの場合はキャッシュを削除
git rm --cached certs/ca.key certs/client.key 2>/dev/null || true
JWT 有効期限の Terraform 設定
resource "aws_cognito_user_pool_client" "app_client" {
# ...
access_token_validity = 1 # 1時間(最小値)
id_token_validity= 1 # 1時間
refresh_token_validity = 30# 30日(用途に応じて短縮)
token_validity_units {
access_token = "hours"
id_token= "hours"
refresh_token = "days"
}
}
6-2. よくあるエラーと解決方法
| エラー | 原因 | 解決方法 |
|---|---|---|
401 Unauthorized | JWT の有効期限切れ | refresh_token で新しい id_token を取得 |
403 Forbidden | Lambda resource policy の設定漏れ | aws_lambda_permission の principal を apigateway.amazonaws.com に設定 |
TLS handshake failed | クライアント証明書の CA が TrustStore と不一致 | openssl verify -CAfile certs/ca.crt certs/client.crt で検証 |
CALLOUT_EXCEPTION | Remote Site Settings 未設定 | Setup → Remote Site Settings で API Gateway URL を許可 |
UserNotFoundException | テストユーザーが存在しない | aws cognito-idp admin-confirm-sign-up でユーザー確認 |
NotAuthorizedException | USER_PASSWORD_AUTH が無効 | Cognito App Client で ALLOW_USER_PASSWORD_AUTH を有効化 |
invalid_grant | Custom Label の値が誤っている | Salesforce Setup → Custom Labels で CognitoPassword を再確認 |
TLS 証明書の検証コマンド
# クライアント証明書が CA によって署名されているか確認
openssl verify -CAfile certs/ca.crt certs/client.crt
# 証明書の有効期限を確認
openssl x509 -in certs/client.crt -noout -dates
# TrustStore の内容確認(API Gateway に登録した PEM)
openssl x509 -in certs/ca.crt -noout -text | grep -E "Subject:|Not After"
Cognito ユーザーの確認
# ユーザーが存在するか確認
aws cognito-idp admin-get-user \
--user-pool-id <USER_POOL_ID> \
--username testuser@example.com \
--region ap-northeast-1
# 管理者権限でパスワードを設定(CONFIRMED 状態にする)
aws cognito-idp admin-set-user-password \
--user-pool-id <USER_POOL_ID> \
--username testuser@example.com \
--password "TestPass123" \
--permanent \
--region ap-northeast-1
6-3. CloudWatch ログの活用
# Lambda ログをリアルタイムで tail
aws logs tail /aws/lambda/handson-cognito-api \
--follow \
--region ap-northeast-1
# 直近30分のログを取得
aws logs tail /aws/lambda/handson-cognito-api \
--since 30m \
--region ap-northeast-1
API Gateway アクセスログの有効化
# ロググループの ARN を確認(または新規作成)
aws logs create-log-group \
--log-group-name /aws/api-gateway/handson-cognito-api \
--region ap-northeast-1
LOG_GROUP_ARN=$(aws logs describe-log-groups \
--log-group-name-prefix /aws/api-gateway/handson-cognito-api \
--query "logGroups[0].arn" \
--output text \
--region ap-northeast-1)
# API Gateway ステージにアクセスログを設定
aws apigatewayv2 update-stage \
--api-id <API_ID> \
--stage-name prod \
--access-log-settings DestinationArn=${LOG_GROUP_ARN} \
--region ap-northeast-1
# Lambda の ERROR ログのみ抽出
aws logs filter-log-events \
--log-group-name /aws/lambda/handson-cognito-api \
--filter-pattern "ERROR" \
--region ap-northeast-1
# JWT 検証失敗(401)を API Gateway ログから確認
aws logs filter-log-events \
--log-group-name /aws/api-gateway/handson-cognito-api \
--filter-pattern "{ $.status = 401 }" \
--region ap-northeast-1
Section 7: クリーンアップ + まとめ
ハンズオンで作成した AWS リソースは 放置すると課金が発生する。必ずクリーンアップを実施すること。
7-1. AWS リソースの削除(Terraform)
cd terraform/
# 削除対象リソースを事前確認(dry run)
terraform plan -destroy
# リソースを一括削除
terraform destroy
# プロンプト: Enter a value: yes
Terraform が削除するリソース(主要なもの):
| リソース | 説明 |
|---|---|
aws_cognito_user_pool | User Pool(ユーザーデータも削除される) |
aws_lambda_function | Lambda 関数 |
aws_apigatewayv2_api | API Gateway HTTP API |
aws_apigatewayv2_domain_name | カスタムドメイン(設定した場合) |
aws_iam_role | Lambda 実行ロール |
aws_cloudwatch_log_group | CloudWatch ロググループ |
注意:
terraform destroyはすべての Cognito ユーザーデータを含む User Pool を削除する。本番環境では事前にユーザーデータのバックアップを取得すること。
7-2. ローカル証明書ファイルの削除
# CA 関連ファイルの削除
rm -f certs/ca.key certs/ca.crt certs/ca.srl
# クライアント証明書関連ファイルの削除
rm -f certs/client.key certs/client.csr certs/client.crt
# 削除確認
ls -la certs/
7-3. Salesforce 側のクリーンアップ
1. Named Credentials の削除:
Setup → Named Credentials → AWS_Cognito_API → 「Del」
2. Remote Site Settings の削除:
Setup → Remote Site Settings → AWS_Cognito_API → 「Del」
3. Custom Labels の削除(任意):
Setup → Custom Labels → CognitoUsername / CognitoPassword / CognitoClientId → 「Del」
4. Apex クラスの削除(任意):
Setup → Apex Classes → CognitoAuthService / CognitoApiService / CognitoApiServiceTest → 「Del」
7-4. 学習内容の振り返り
本ハンズオンで習得したスキルのチェックリスト:
- [ ] Cognito User Pool と Identity Pool の役割の違いを説明できる
- [ ] mTLS と通常 TLS の違い(双方向証明書検証)を説明できる
- [ ] OpenSSL で CA 証明書とクライアント証明書を生成できる
- [ ] API Gateway HTTP API に JWT Authorizer を設定できる
- [ ] Terraform で Cognito + Lambda + API GW を一括構築・破棄できる
- [ ] Salesforce Apex から Named Credential 経由で AWS API を呼び出せる
- [ ] HttpCalloutMock を使った Apex テストクラスを実装できる
- [ ] セキュリティリスク(CA鍵漏洩・JWT長期有効・mTLSバイパス)と対策を説明できる
- [ ] CloudWatch Logs で Lambda および API Gateway のデバッグができる
7-5. 関連記事シリーズ
前提知識
- Terraform 基礎 — IaC 入門ハンズオン: Terraform 基礎 — IaC入門ハンズオン
本記事の Terraform コードを理解するための基礎編。terraform initからterraform destroyまで、IaC の基本を習得できる。
AWS Step Functions シリーズ(AWS サービス連携の応用)
第1回: AWS Step Functions 入門: AWS Step Functions 入門 — 初めてのステートマシン
Step Functions の概念から初めてのステートマシン作成まで。本 Cognito ハンズオンと組み合わせ、認証済みユーザーのアクションをワークフローで処理するパターンを学べる。第8回: Step Functions SDK Direct Integration: Step Functions SDK Direct Integration — AWS サービスを直接呼び出す
Lambda を介さずに Cognito ユーザー管理を Step Functions から直接実行するパターンを学べる。
7-6. 次のステップ
セキュリティ強化
ACM プライベート CA による証明書自動管理
# ACM Private CA の作成(AWS CLI)
aws acm-pca create-certificate-authority \
--certificate-authority-configuration \
file://ca-config.json \
--certificate-authority-type ROOT \
--region ap-northeast-1
AWS WAF + API Gateway によるさらなるセキュリティ強化
# WAF Web ACL を API Gateway に関連付け(Terraform)
resource "aws_wafv2_web_acl_association" "api_gw" {
resource_arn = aws_apigatewayv2_stage.prod.arn
web_acl_arn = aws_wafv2_web_acl.main.arn
}
IP レート制限・SQL インジェクション防御・地理的ブロックを追加し、多層防御を実現する。
Salesforce との高度な連携
Salesforce Platform Events との非同期連携パターン
Salesforce Platform Event → Amazon EventBridge → Lambda → API Response
Salesforce External Services(OpenAPI 統合)
OpenAPI 仕様書を Salesforce にインポートすることで、Apex コードを書かずに Flow や Process Builder から直接 AWS API を呼び出せる。
おわりに
本ハンズオンでは、Amazon Cognito × Salesforce Apex × mTLS という3つの技術スタックを組み合わせ、エンタープライズグレードのセキュアな API 連携を構築した。
| フェーズ | 達成内容 |
|---|---|
| Section 1 | Cognito User Pool / Identity Pool / mTLS / OAuth 2.0 の概念理解 |
| Section 2 | アーキテクチャ設計(認証フロー・mTLS フロー・コスト試算) |
| Section 3 | AWS コンソールでの手動構築(Cognito + Lambda + API GW) |
| Section 4 | Terraform による Infrastructure as Code 化 |
| Section 5 | Salesforce Apex(Named Credential + CognitoAuthService + CognitoApiService)実装 |
| Section 6 | セキュリティ Tips + トラブルシューティング |
| Section 7 | クリーンアップ + 次のステップ |
単なる「つながる」だけでなく、証明書管理・トークンライフサイクル・テスト設計・運用監視まで含めた本番品質の実装を体験した。このハンズオンで得たパターンは、Salesforce 以外のエンタープライズシステム(SAP・ServiceNow・Microsoft 365)と AWS を安全に連携する際にも応用できる。
AWS と Salesforce の二大クラウドプラットフォームをセキュアに結合する技術を習得した皆さんのプロジェクトが成功することを祈っている。
