Amazon Cognito × Salesforce Apex — mTLSセキュアユーザー管理ハンズオン:相互TLS認証でAPIを完全保護する実践ガイド

目次

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 を正確に理解する

エンタープライズ統合において「APIを安全に呼ぶ」という要件は当たり前になったが、「二重保護」を実現しているシステムは少ない。本記事では Amazon Cognito(JWT認証)+ mTLS(クライアント証明書) の二段構えでAPIを守り、Salesforce Apex から安全に呼び出す実装を Terraform でゼロから構築する。

本セクションは実装に入る前の概念整理だ。Cognito の2大機能・mTLS の仕組み・OAuth 2.0 Client Credentials フローを正確に理解してから手を動かすことで、トラブル発生時の原因切り分けが格段に速くなる。


1-1. Amazon Cognito の全体像

Amazon Cognito は User PoolIdentity Pool という2つの独立したサービスで構成される。混同が最も多いポイントなので、先に表で整理する。

機能User PoolIdentity 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種類の認証フローがある。

  1. USER_PASSWORD_AUTH
    ユーザー名とパスワードをそのまま送信。シンプルだが、ネットワーク上でパスワードが平文に近い形で送られるため、本番では非推奨。

  2. USER_SRP_AUTH(SRP: Secure Remote Password)
    パスワードを直接送らず、数学的な proof を交換してサーバーがパスワードを検証する。MITMに強く、本番推奨。

  3. 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_tokenAPIアクセス認可(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 CodeWebアプリのユーザーログインあり
ImplicitSPA(非推奨)あり
Client CredentialsM2M / サーバー間通信なし
Device CodeTV・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 PoolM2Mクライアント(App Client)管理・JWT発行
Amazon Cognito Identity Pool(参考)IAMロール付与(本記事では未使用)
Amazon API Gateway HTTP APIカスタムドメイン+mTLS+JWT Authorizer
AWS LambdaバックエンドAPIロジック
AWS Certificate Manager (ACM)サーバー証明書・クライアント証明書管理
Amazon S3TrustStore(CA証明書PEM)格納
Salesforce ApexNamed 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 CredentialsM2M用フロー。Salesforce が access_token を取得して API を呼ぶ
API GW JWT AuthorizerJWKS 経由で 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 ApexAPIを呼び出すクライアント。mTLS用クライアント証明書を保持し、JWTをAuthorizationヘッダーに付与してリクエストを送信する
AWS ACMクライアント証明書・サーバー証明書を発行・管理する認証局
Amazon API GatewaymTLS検証(TrustStoreによるクライアント証明書検証)+ JWT Authorizer(Cognito JWKSを参照してJWT署名検証)を担う。二重検証により高いセキュリティを実現
Amazon S3CA TrustStore(trust-store.pem)を格納。API GatewayがmTLS検証時に参照する
AWS Lambdaビジネスロジックを実行。Cognitoから取得したJWTのクレームを利用して認可判定を行う
Amazon Cognito User Poolユーザー認証基盤。JWTトークン(IdToken / AccessToken)を発行する
Amazon Cognito Identity PoolCognitoユーザーにAWSリソースへのアクセス権限(一時認証情報)を付与する
AWS STSIdentity 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メールメッセージを送信
検証属性email

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」 タブを選択し、以下を入力します:

項目設定値
ユーザープールIDap-northeast-1_XXXXXXXXX(2-2でメモした値)
アプリクライアントIDXXXXXXXXXXXXXXXXXXXXXXXXXX(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認証フロー

上図はCognito User Poolを使ったJWT認証フローを示しています。

JWTトークンの種類と用途

トークン有効期限用途
IdToken1時間(デフォルト)ユーザー属性情報(email, sub等)を含む。API GW JWT Authorizerが検証
AccessToken1時間(デフォルト)Cognitoリソースへのアクセス権限スコープを含む
RefreshToken30日(デフォルト)IdToken/AccessTokenの更新に使用。安全に保管すること

セキュリティポイント: IdTokenは aud クレームでアプリクライアントIDが確認できます。API GWのJWT Authorizerは iss(Issuer)と aud(Audience)の両方を検証します。


2-6. mTLS TLSハンドシェイクの詳細

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-clientUSER_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内容使用サービス
AmTLS証明書生成(CA証明書 + クライアント証明書)OpenSSL
B-1S3 TrustStore設定Amazon S3
B-2Lambda実行ロール作成IAM
B-3Lambda関数デプロイAWS Lambda
B-4API 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 40964096bitCA秘密鍵。サーバー側CAとして4096bitを推奨
req -new -x509自己署名証明書を直接生成(CSR不要)
-days 365365日本番環境では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

確認ポイント:
IssuerCN=handson-ca(A-1で生成したCA)であること
SubjectCN=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を付けてください。

[スクリーンショット: S3バケット作成完了画面][スクリーンショット: バージョニング有効化確認画面]

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 APIREST 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 hostAPI_IDまたはリージョンのミスエンドポイントURLを再確認
[スクリーンショット: curlコマンドの成功レスポンス]

Section 3 まとめ

このセクションで構築したコンポーネント:

コンポーネントリソース名用途
S3バケットhandson-mtls-truststore-YOURNAMECA証明書(TrustStore)格納
IAMロールhandson-lambda-exec-roleLambda実行ロール
Lambda関数handson-cognito-apiJWTクレーム確認API
HTTP APIhandson-cognito-apiAPI Gatewayエンドポイント
JWT Authorizercognito-authorizerCognito 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 Poolaws_cognito_user_pool
Cognito App Clientaws_cognito_user_pool_client
Cognito Identity Poolaws_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 APIaws_apigatewayv2_api
JWT Authorizeraws_apigatewayv2_authorizer
ルート・統合・ステージaws_apigatewayv2_route / _integration / _stage

次のSection 5では、TerraformのOutputで取得した user_pool_iduser_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=passwordCognito の 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_APINamed Credential を参照するプレフィックス構文
NAMED_CRED + '/users/me'Named Credential の URL + パスを結合してエンドポイントを指定
Authorization: Bearer {idToken}API Gateway の JWT Authorizer が検証するヘッダー
Label.CognitoUsernameCustom 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 UnauthorizedJWT の有効期限切れrefresh_token で新しい id_token を取得
403 ForbiddenLambda resource policy の設定漏れaws_lambda_permissionprincipalapigateway.amazonaws.com に設定
TLS handshake failedクライアント証明書の CA が TrustStore と不一致openssl verify -CAfile certs/ca.crt certs/client.crt で検証
CALLOUT_EXCEPTIONRemote Site Settings 未設定Setup → Remote Site Settings で API Gateway URL を許可
UserNotFoundExceptionテストユーザーが存在しないaws cognito-idp admin-confirm-sign-up でユーザー確認
NotAuthorizedExceptionUSER_PASSWORD_AUTH が無効Cognito App Client で ALLOW_USER_PASSWORD_AUTH を有効化
invalid_grantCustom 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_poolUser Pool(ユーザーデータも削除される)
aws_lambda_functionLambda 関数
aws_apigatewayv2_apiAPI Gateway HTTP API
aws_apigatewayv2_domain_nameカスタムドメイン(設定した場合)
aws_iam_roleLambda 実行ロール
aws_cloudwatch_log_groupCloudWatch ロググループ

注意: 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 サービス連携の応用)


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 1Cognito User Pool / Identity Pool / mTLS / OAuth 2.0 の概念理解
Section 2アーキテクチャ設計(認証フロー・mTLS フロー・コスト試算)
Section 3AWS コンソールでの手動構築(Cognito + Lambda + API GW)
Section 4Terraform による Infrastructure as Code 化
Section 5Salesforce Apex(Named Credential + CognitoAuthService + CognitoApiService)実装
Section 6セキュリティ Tips + トラブルシューティング
Section 7クリーンアップ + 次のステップ

単なる「つながる」だけでなく、証明書管理・トークンライフサイクル・テスト設計・運用監視まで含めた本番品質の実装を体験した。このハンズオンで得たパターンは、Salesforce 以外のエンタープライズシステム(SAP・ServiceNow・Microsoft 365)と AWS を安全に連携する際にも応用できる。

AWS と Salesforce の二大クラウドプラットフォームをセキュアに結合する技術を習得した皆さんのプロジェクトが成功することを祈っている。

最新情報をチェックしよう!