7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オンプレミスからプライベートなAPI Gatewayを呼ぶ ― CognitoのTokenエンドポイントをAPI Gatewayでプロキシする構成

7
Posted at

本記事は、BIPROGY / ユニアデックス社内AWSコミュニティ「BIPROGY AWS SPARK」の定期投稿企画第3回目の記事です。他の定期投稿企画の記事は、インデックスページをご覧ください。

はじめに

オンプレミスの社内システムから、AWSのVPC内に構成したプライベートなAmazon API GatewayのAPIを呼び出したい、という場合があります。このAPIがAmazon Cognitoのマシンツーマシン(M2M)認証で保護されている場合、ひとつ問題が発生します。CognitoのTokenエンドポイントはインターネット経由でしか呼び出せないため、閉域網にいるオンプレミスのシステムはアクセストークンを取得できないのです。

本記事では、この問題を Amazon API Gatewayに/tokenエンドポイントを構成してCognitoのTokenエンドポイントをHTTPプロキシさせる ことで解決する構成を紹介し、検証環境での実行例とともに解説します。

対象読者

  • AWSやクラウドを学習中の人
  • 実務で「閉域網からCognito認証付きのプライベートAPIを呼びたい」という課題に直面した人

課題: CognitoのTokenエンドポイントはインターネット経由でしか呼べない

まず前提となる構成を整理します。

  • オンプレミスとVPCはAWS Direct ConnectおよびAWS Transit Gatewayでプライベートな接続を確立している
  • API GatewayはプライベートREST APIとして構成し、VPC内のインターフェイスVPCエンドポイント(execute-api)経由でのみ呼び出せる
  • APIはAmazon CognitoのオーソライザーでM2M認証(OAuth 2.0 クライアントクレデンシャルフロー)により保護されている

クライアントクレデンシャルフローでは、クライアントはまずCognitoのTokenエンドポイントからアクセストークンを取得する必要があります。

https://<ドメイン>.auth.<リージョン>.amazoncognito.com/oauth2/token

このTokenエンドポイントはパブリックなエンドポイントです。インターネットへの経路を持たない閉域のオンプレミス環境からは到達できないため、トークンが取得できず、APIを呼び出せません。

なお、2025年11月にAmazon CognitoユーザープールのAWS PrivateLink対応が発表されましたが、クライアントクレデンシャルフロー(M2M認可)を含むOAuth 2.0の各フローは、現時点でVPCエンドポイント経由ではサポートされていません。PrivateLinkで対応できるのはユーザープールの管理系APIや一部の認証フローに限られるため、本記事のパターンは引き続き有効です。

解決策: API Gatewayに/tokenプロキシを構成する

解決策はシンプルで、プライベートなAPI Gatewayに/tokenリソースを追加し、CognitoのTokenエンドポイントへのHTTPプロキシ統合を構成することです。

ポイントは、API GatewayのHTTP統合によるバックエンドへのリクエストはAWS側のネットワークから発行されることです。クライアント(オンプレミス)側にインターネット到達性がなくても、API GatewayがCognitoのTokenエンドポイントを代わりに呼び出してくれます。

構成イメージ

ここでは/exampleとして、VPC Link v2経由でALB→Amazon ECSで構成したAPIを公開する例を示しています。バックエンドのALBやECS、公開するAPIリソースはあくまで一例で、実際に何を公開するかは要件に応じて構成してください。

APIリソース 統合 認証
/token CognitoのTokenエンドポイントへのHTTPプロキシ統合 なし(client_id/client_secretで認証されるため)
/example Cognitoオーソライザーで保護したAPI Cognitoオーソライザー

全体の呼び出しフローは次のとおりです。

  1. オンプレミスのシステムが、Direct Connect → Transit Gateway → VPCエンドポイントを経由して、API GatewayのPOST /tokenを呼び出す(client_id/client_secretをBasic認証で送信)
  2. API GatewayがリクエストをCognitoのTokenエンドポイント(/oauth2/token)へプロキシし、アクセストークンがオンプレミスに返る
  3. オンプレミスのシステムが、取得したアクセストークンをAuthorizationヘッダーに付与して/exampleを呼び出す
  4. Cognitoオーソライザーがトークンを検証し、検証に成功したリクエストのみバックエンド(この例ではALB→ECS)に到達する

オンプレミスから見た通信はプライベートな経路(Direct Connect / Transit Gateway / VPCエンドポイント)に閉じており、オンプレミス側にインターネットへの経路は不要です。なお、API GatewayからCognitoへの通信はAWS側から公開Token EndpointへHTTPSで行われます。

動作確認用の検証環境(疑似構成)について

本検証ではDirect ConnectやTransit Gatewayを用意せず、次の疑似構成を使います。

検証用の疑似構成

実環境 疑似環境での代替
オンプレミスのシステム インターネット非接続のVPC内に配置したAmazon EC2インスタンス
Direct Connect / Transit Gateway (VPC内で完結するため不要)
オンプレミスからの操作 AWS Systems Manager Session Manager(VPCエンドポイント経由)でEC2に接続
ALB→ECSのバックエンド API Gatewayの/mockリソース(モック統合+Cognitoオーソライザー)

EC2インスタンスはパブリックIPを持たず、VPCにはインターネットゲートウェイもNATゲートウェイも構成しません。「インターネットに出られないクライアント」という条件を再現したうえで、execute-apiのVPCエンドポイント経由でのみAPI Gatewayへ到達できる状態にします。

なお本記事の疑似環境は動作確認用であり、セキュリティ設定は本番運用の水準では考慮していない点にご留意ください。実際に構築する際は最小権限などのベストプラクティスに従ってください。

検証環境の構築手順(AWS CLI)

主要リソースの構築手順をAWS CLIで示します。リージョンはap-northeast-1を想定しています。VPC、サブネット、セキュリティグループ、EC2(SSM用のIAMロール付き)、SSM用VPCエンドポイント(ssm / ssmmessages)は構築済みの前提です。

1. Cognitoリソースの作成

# ユーザープールの作成
USER_POOL_ID=$(aws cognito-idp create-user-pool \
  --pool-name m2m-test-pool \
  --query 'UserPool.Id' --output text)

# リソースサーバーとカスタムスコープの作成
aws cognito-idp create-resource-server \
  --user-pool-id "$USER_POOL_ID" \
  --identifier my-api \
  --name my-api \
  --scopes ScopeName=read,ScopeDescription="Read access"

# アプリクライアントの作成(クライアントクレデンシャルフローを許可)
aws cognito-idp create-user-pool-client \
  --user-pool-id "$USER_POOL_ID" \
  --client-name m2m-client \
  --generate-secret \
  --allowed-o-auth-flows client_credentials \
  --allowed-o-auth-scopes "my-api/read" \
  --allowed-o-auth-flows-user-pool-client \
  --explicit-auth-flows ALLOW_REFRESH_TOKEN_AUTH

# Tokenエンドポイント用のドメインの作成
aws cognito-idp create-user-pool-domain \
  --user-pool-id "$USER_POOL_ID" \
  --domain my-m2m-test-domain

作成したアプリクライアントのClientIdClientSecretは動作確認で使うので控えておきます。

2. execute-api用VPCエンドポイントの作成

VPCE_ID=$(aws ec2 create-vpc-endpoint \
  --vpc-id "$VPC_ID" \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.ap-northeast-1.execute-api \
  --subnet-ids "$SUBNET_ID" \
  --security-group-ids "$SG_ID" \
  --private-dns-enabled \
  --query 'VpcEndpoint.VpcEndpointId' --output text)

プライベートDNSを有効にすると、VPC内からhttps://{api-id}.execute-api.ap-northeast-1.amazonaws.comという通常のドメイン名でプライベートAPIを呼び出せます。

3. プライベートREST APIの作成

リソースポリシーで、作成したVPCエンドポイントからの呼び出しのみを許可します。
json内のvpce-xxxxxxxxxxxxxxxxxは作成したexecute-api用VPCエンドポイントのIDに変更してください。

resource-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "execute-api:Invoke",
      "Resource": "execute-api:/*",
      "Condition": {
        "StringEquals": { "aws:SourceVpce": "vpce-xxxxxxxxxxxxxxxxx" }
      }
    }
  ]
}
API_ID=$(aws apigateway create-rest-api \
  --name cognito-token-proxy \
  --endpoint-configuration types=PRIVATE,vpcEndpointIds="$VPCE_ID" \
  --policy file://resource-policy.json \
  --query 'id' --output text)

ROOT_ID=$(aws apigateway get-resources --rest-api-id "$API_ID" \
  --query 'items[?path==`/`].id' --output text)

4. /tokenリソース(CognitoへのHTTPプロキシ統合)

TOKEN_RESOURCE_ID=$(aws apigateway create-resource \
  --rest-api-id "$API_ID" --parent-id "$ROOT_ID" \
  --path-part token --query 'id' --output text)

aws apigateway put-method \
  --rest-api-id "$API_ID" --resource-id "$TOKEN_RESOURCE_ID" \
  --http-method POST --authorization-type NONE

aws apigateway put-integration \
  --rest-api-id "$API_ID" --resource-id "$TOKEN_RESOURCE_ID" \
  --http-method POST \
  --type HTTP_PROXY --integration-http-method POST \
  --uri "https://my-m2m-test-domain.auth.ap-northeast-1.amazoncognito.com/oauth2/token"

HTTP_PROXY統合なので、基本的にはリクエストを透過的に転送します。

5. Cognitoオーソライザーと/mockリソース

${ACCOUNT_ID}は適宜設定してください。

# Cognitoオーソライザーの作成
AUTHORIZER_ID=$(aws apigateway create-authorizer \
  --rest-api-id "$API_ID" \
  --name cognito-authorizer --type COGNITO_USER_POOLS \
  --provider-arns "arn:aws:cognito-idp:ap-northeast-1:${ACCOUNT_ID}:userpool/${USER_POOL_ID}" \
  --identity-source method.request.header.Authorization \
  --query 'id' --output text)

# /mockリソースの作成
MOCK_RESOURCE_ID=$(aws apigateway create-resource \
  --rest-api-id "$API_ID" --parent-id "$ROOT_ID" \
  --path-part mock --query 'id' --output text)

# メソッドにオーソライザーとスコープを設定
aws apigateway put-method \
  --rest-api-id "$API_ID" --resource-id "$MOCK_RESOURCE_ID" \
  --http-method GET \
  --authorization-type COGNITO_USER_POOLS \
  --authorizer-id "$AUTHORIZER_ID" \
  --authorization-scopes "my-api/read"

# モック統合(200を返すだけのバックエンド)
aws apigateway put-integration \
  --rest-api-id "$API_ID" --resource-id "$MOCK_RESOURCE_ID" \
  --http-method GET --type MOCK \
  --request-templates '{"application/json": "{\"statusCode\": 200}"}'

aws apigateway put-method-response \
  --rest-api-id "$API_ID" --resource-id "$MOCK_RESOURCE_ID" \
  --http-method GET --status-code 200

aws apigateway put-integration-response \
  --rest-api-id "$API_ID" --resource-id "$MOCK_RESOURCE_ID" \
  --http-method GET --status-code 200 \
  --response-templates '{"application/json": "{\"message\": \"Hello from private API\"}"}'

ここで重要なのが--authorization-scopesです。この設定がないと署名と有効期限しか検証されず、同じユーザープールの有効なトークンならすべて通ってしまいます。スコープを設定することでscopeクレームまで検証され、クライアント単位の認可が効くようになります。

6. デプロイ

aws apigateway create-deployment \
  --rest-api-id "$API_ID" --stage-name v1

動作確認

Session ManagerでEC2インスタンスに接続し、動作を確認します。

1. /tokenでアクセストークンを取得する

client_idとclient_secretをBasic認証ヘッダーにして、grant_type=client_credentialsでPOSTします。
client_idとclient_secretは適宜取得して設定してください。

CLIENT_ID="xxxxxxxxxxxxxxxxxxxxxxxxxx"
CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASIC_AUTH=$(echo -n "${CLIENT_ID}:${CLIENT_SECRET}" | base64 -w0)

curl -s -X POST \
  "https://${API_ID}.execute-api.ap-northeast-1.amazonaws.com/v1/token" \
  -H "Authorization: Basic ${BASIC_AUTH}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&scope=my-api/read"

期待される結果: API Gateway経由でCognitoからアクセストークンが返ります。インターネットに出られないEC2からでも、プライベートな経路だけでトークンを取得できていることがポイントです。

{
  "access_token": "eyJraWQiOiJxxxxxxxx...(省略)",
  "expires_in": 3600,
  "token_type": "Bearer"
}

2. アクセストークンで保護されたAPIを呼び出す

TOKEN=$(curl -s -X POST \
  "https://${API_ID}.execute-api.ap-northeast-1.amazonaws.com/v1/token" \
  -H "Authorization: Basic ${BASIC_AUTH}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&scope=my-api/read" \
  | sed -e 's/.*"access_token":"\([^"]*\)".*/\1/')

curl -s -w '\nHTTP Status: %{http_code}\n' \
  "https://${API_ID}.execute-api.ap-northeast-1.amazonaws.com/v1/mock" \
  -H "Authorization: Bearer ${TOKEN}"

期待される結果: Cognitoオーソライザーの検証を通過し、HTTP 200でレスポンスが返ります。

{"message": "Hello from private API"}
HTTP Status: 200

3. (異常系)トークンなしで呼び出す

curl -s -w '\nHTTP Status: %{http_code}\n' \
  "https://${API_ID}.execute-api.ap-northeast-1.amazonaws.com/v1/mock"

期待される結果: Cognitoオーソライザーで拒否され、HTTP 401が返ります。期限切れや不正なトークンを指定した場合も同様です。

{"message":"Unauthorized"}
HTTP Status: 401

4. (補足)CognitoのTokenエンドポイントに直接アクセスしてみる

curl -s -m 10 -X POST \
  "https://my-m2m-test-domain.auth.ap-northeast-1.amazoncognito.com/oauth2/token" \
  -H "Authorization: Basic ${BASIC_AUTH}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&scope=my-api/read"

期待される結果: インターネットへの経路がないため接続できず、タイムアウトします。この環境が閉域であり、①のトークン取得がAPI Gatewayのプロキシによって実現できていることの裏付けになります。

curl: (28) Failed to connect to my-m2m-test-domain.auth.ap-northeast-1.amazoncognito.com port 443 after 10001 ms: Timeout was reached

5. 検証環境の削除

検証が終わったら、不要な費用が発生しないよう作成したAWSリソース(Cognitoユーザープール、API Gateway、VPCエンドポイント、EC2など)は忘れずに削除してください。

実運用上の考慮事項

CognitoのTokenエンドポイントのセキュリティ(AWS WAFによる保護)

/tokenプロキシを構成しても、CognitoのTokenエンドポイント自体はパブリックに公開されたままです。CognitoユーザープールにはAWS WAFのWeb ACLを関連付けることができ、Bot Controlなどのマネージドルールグループを含めてTokenエンドポイントを直接保護できます。

さらに、AWSセキュリティブログのHow to monitor, optimize, and secure Amazon Cognito machine-to-machine authorizationでは、API Gatewayプロキシ経由のトークンリクエストのみを許可する構成が紹介されています。

  • API Gatewayの統合リクエストで、シークレット値を持つカスタムHTTPヘッダーを注入する
  • WAF側で「そのヘッダーを持つリクエストのみ許可(優先度0)+それ以外はすべてブロック(優先度1)」のルールを構成する

これにより、Cognitoの/oauth2/tokenへの直接アクセスを遮断し、正規の経路をAPI Gatewayの/tokenプロキシに一本化できます。本構成のようにクライアントがすべてプロキシ経由でトークンを取得する設計だからこそ、直接アクセスを塞げるという利点があります。

まとめ

  • CognitoのTokenエンドポイントはパブリックなエンドポイントであり、閉域のオンプレミス環境からは直接呼び出せない(PrivateLink対応後もM2MのOAuthフローは対象外)
  • プライベートなAPI Gatewayに/tokenリソースを構成し、CognitoのTokenエンドポイントへHTTPプロキシ統合することで、Direct Connect / Transit Gateway経由のプライベートな経路だけでM2M認証とAPI呼び出しを完結できる
  • CognitoのTokenエンドポイント自体はAWS WAFで保護し、API Gatewayプロキシ経由のリクエストのみ許可する構成にすることでセキュリティを強化できる

同じ課題に直面した方の参考になれば幸いです。

参考情報

BIPROGYグループの技術への取り組み

We Are Hiring!

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?