AWSハンズオン - Cognito + API Gateway + Lambda で認証付きAPIをSAMで構築しよう【コンソール版との比較付き】

AWS Basic
スポンサーリンク
スポンサーリンク

はじめに

「認証付きAPIの構成をコードで管理して、チームで再現できるようにしたい」——そんな要件に、AWS SAM(Serverless Application Model) は最適な選択肢のひとつです。

この記事では、AWS SAM を使って、Cognito + API Gateway + Lambda による認証付き REST API をゼロから構築するハンズオンを紹介します。

クライアント(curl)
  │
  ├─ GET /hello  → API Gateway(認証なし)→ Lambda → 200 OK
  │
  └─ GET /profile
        ├─ Authorization ヘッダーなし → API Gateway が 401 を返す
        └─ Authorization: <IDトークン>
              → Cognito Authorizer が JWT を検証(Lambda 不要)
              → Lambda → email / sub を返す → 200 OK

このハンズオンで体験できること:

  • AWS::Cognito::UserPoolAWS::Cognito::UserPoolClient の SAM 定義
  • DefaultAuthorizer を使ったエンドポイント単位の認証制御(1行で全エンドポイントに認証を適用)
  • Authorizer: NONE で特定エンドポイントだけ認証を除外するパターン
  • CLI でのテスト実行(ユーザー作成 → IDトークン取得 → 認証済みリクエスト)

このハンズオンの特徴:

  • sam build + sam deploy2コマンドで全リソースをデプロイ
  • Cognito User Pool / User Pool Client / API Gateway / Lambda / IAM ロールを template.yaml 1ファイルで管理
  • sam delete全リソースを一括削除(コンソール版では各リソースを個別に削除する必要がある)

Amazon検索[本 AWS 開発]

スポンサーリンク

SAM vs コンソール:どれだけ違うか

比較項目SAM(コード)コンソール(手動)
Cognito Client 作成GenerateSecret: false の1行でシークレットなしクライアントを定義新UIで「SPA」タイプのクライアントを別途手動作成
Authorizer の紐付けUserPoolArn: !GetAtt UserPool.Arn で自動連携コンソールで User Pool を手動選択し、トークンのソースを手入力
エンドポイント別認証DefaultAuthorizer + 除外箇所に Authorizer: NONEエンドポイントごとにオーソライザーを手動設定
削除sam delete 1コマンドAPI Gateway / Lambda / Cognito / IAM を個別に削除
再現性チームで同じ環境を即座に再現できる手順書が必要

スポンサーリンク

キーワード解説

用語意味
User PoolCognito のユーザーディレクトリ。ユーザー登録・認証・JWT 発行を担う
User Pool Clientアプリが User Pool に接続するための設定。Client ID を持つ
IDトークンログイン成功時に Cognito が発行する JWT。email / sub などのユーザー情報を含む
Cognito AuthorizerAPI Gateway が IDトークンを検証する認可機能。Lambda コードが不要で動作する
claimsJWT ペイロードに含まれる情報(email / sub / token_use など)
DefaultAuthorizer全エンドポイントに一括でオーソライザーを適用する SAM の設定。個別に Authorizer: NONE で除外できる

前提条件

ツール確認コマンド最低バージョン目安
AWS CLI v2aws --version2.x
AWS SAM CLIsam --version1.x
Python 3.12python --version3.12
aws sts get-caller-identity

アカウントIDが表示されれば認証設定済みです。


使用するAWSサービス

サービス役割料金
Amazon Cognitoユーザー管理・認証・JWT発行月5万MAUまで無料
API GatewayREST API のエンドポイント管理・認可月100万リクエストまで無料(無料期間12ヶ月)
LambdaAPIのビジネスロジック(1関数)月100万リクエスト・400,000 GB-秒まで無料
IAMLambda の実行権限管理無料
S3SAM デプロイパッケージ置き場少量のため実質無料
CloudWatch LogsLambda の実行ログ月5GBまで無料

Step 1: プロジェクトフォルダを開く

cd C:\my-aws\aws-learning-projects\cognito-api-lambda

フォルダ構造

cognito-api-lambda/
├── template.yaml       # SAMテンプレート(Cognito + API Gateway + Lambda 定義)
├── samconfig.toml      # デプロイ設定(gitignore 対象・毎回手動作成が必要)
├── docs/
│   ├── 1_console.md    # AWSコンソール版手順
│   └── 2_sam.md        # SAM版手順
└── src/
    └── app.py          # Lambda 関数(/hello と /profile を処理)

Step 2: SAMテンプレートの確認(template.yaml)

template.yaml は全 AWSリソースの設計図です。ポイントを確認しておきます。

Cognito リソースの定義

Resources:
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UsernameAttributes: [email]     # メールアドレスでログイン
      Policies:
        PasswordPolicy:
          MinimumLength: 8            # 記号・大文字不要(テスト用に緩和)

  # アプリクライアント: CLI から initiate-auth で使う
  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      GenerateSecret: false           # シークレットなし(CLI テスト用)
      ExplicitAuthFlows:
        - ALLOW_USER_PASSWORD_AUTH    # username/password で直接認証(テスト用)
        - ALLOW_REFRESH_TOKEN_AUTH

API Gateway と Cognito Authorizer の定義

  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      Auth:
        # DefaultAuthorizer を指定 → 全エンドポイントにデフォルトで認証が必要になる
        DefaultAuthorizer: CognitoAuthorizer
        Authorizers:
          CognitoAuthorizer:
            UserPoolArn: !GetAtt UserPool.Arn  # User Pool と自動的に紐付け
            Identity:
              Header: Authorization            # JWT を Authorization ヘッダーで受け取る

Lambda 関数とエンドポイントの定義

  ApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      Events:
        PublicEndpoint:              # /hello: 公開エンドポイント
          Type: Api
          Properties:
            Path: /hello
            Method: get
            Auth:
              Authorizer: NONE       # DefaultAuthorizer を明示的に無効化

        PrivateEndpoint:             # /profile: 認証必須
          Type: Api
          Properties:
            Path: /profile
            Method: get
            # Auth 指定なし → DefaultAuthorizer(CognitoAuthorizer)が自動適用

template.yaml のポイント解説

ポイント説明
GenerateSecret: falseCLI テスト用にシークレットなしのクライアントを定義。コンソール版では SPA タイプのクライアントを手動作成
DefaultAuthorizer全エンドポイントに Cognito 認証をデフォルト適用。コンソール版ではエンドポイントごとに手動設定
Authorizer: NONE/hello など特定エンドポイントの認証を除外。コンソール版では /hello に Authorizer を設定しないことに対応
UserPoolArn: !GetAtt UserPool.ArnCognito Authorizer と User Pool を自動的に紐付け。コンソール版では手動でドロップダウン選択が必要

Step 3: Lambda コードの確認(src/app.py)

src/app.py の構造を把握しておきます。

import json


def lambda_handler(event, context):
    resource = event.get("resource", "/")   # API Gateway プロキシ統合: リクエストパスが入る

    if resource == "/hello":
        return {"statusCode": 200, "body": json.dumps({"message": "Hello! This is a public endpoint."})}

    if resource == "/profile":
        # Cognito Authorizer が JWT を検証済み
        # JWT ペイロードが event["requestContext"]["authorizer"]["claims"] に格納される
        claims = (
            event.get("requestContext", {})
            .get("authorizer", {})
            .get("claims", {})
        )
        return {
            "statusCode": 200,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps(
                {
                    "message": "認証成功",
                    "email": claims.get("email"),
                    "sub": claims.get("sub"),          # Cognito が生成したユーザーの一意 ID
                    "token_use": claims.get("token_use"),  # "id" が返る(IDトークンを使っているため)
                },
                ensure_ascii=False,
            ),
        }

    return {"statusCode": 404, "body": json.dumps({"message": "Not Found"})}

app.py のポイント:

  • Lambda 側でトークン検証は一切不要。Cognito Authorizer が自動で行う
  • claims には email / sub / token_use など JWT ペイロードの全フィールドが格納される
  • /hello への呼び出しでは claims は空(Authorizer が動作しないため)

各コードの全文はコンソール版記事を参照してください。
SAM版・コンソール版で使用する Lambda コードは共通です。コンソール版では手順とあわせてコードを全文掲載しています。

AWSコンソールだけでCognito + API Gateway + Lambda の認証付きAPIを構築する手順


Step 4: samconfig.toml を作成する

samconfig.toml.gitignore で管理外のため、毎回手動で作成する必要があります。

cognito-api-lambda/samconfig.toml を新規作成して以下を貼り付けます。

version = 0.1

[default.deploy.parameters]
stack_name = "cognito-api-lambda-stack"
resolve_s3 = true
s3_prefix = "cognito-api-lambda-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
disable_rollback = true
image_repositories = []

[default.global.parameters]
region = "ap-northeast-1"

samconfig.toml の置き場所:
cognito-api-lambda/ フォルダの直下に置く必要があります。


Step 5: sam build(ビルド)

cd C:\my-aws\aws-learning-projects\cognito-api-lambda
sam build

成功すると以下のように表示されます。

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

sam build が行っていること:

  • src/ フォルダを ZIP パッケージ化して .aws-sam/build/ に配置
  • template.yaml.aws-sam/build/template.yaml にコピー
  • Lambda デプロイの準備を整える

Step 6: sam deploy(デプロイ)

sam deploy

変更内容が表示されて確認を求められます。

Deploy this changeset? [y/N]: y

y を入力して進めます。数分でデプロイが完了します。

デプロイ完了の確認

ターミナルに Outputs が表示されます。

Outputs
----------------------------------------------------------------------
Key    ApiUrl
Value  https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod

Key    UserPoolId
Value  ap-northeast-1_XXXXXXXX

Key    UserPoolClientId
Value  XXXXXXXXXXXXXXXXXXXXXXXXXX
----------------------------------------------------------------------

3つの値を全て控えておきます。(テスト実行コマンドで使用)

デプロイされるリソース一覧

Cognito User Pool × 1              : cognito-api-lambda-stack-user-pool
Cognito User Pool Client × 1       : シークレットなし(ALLOW_USER_PASSWORD_AUTH 有効)
API Gateway REST API × 1           : cognito-api-lambda-stack + Prod ステージ
Lambda 関数 × 1                    : cognito-api-lambda-stack-ApiFunction-XXXX
IAM ロール × 1                     : Lambda 実行ロール
S3                                 : SAM デプロイパッケージ
CloudWatch Logs                    : Lambda 自動生成ログ × 1

Step 7: 動作テスト

事前準備: 変数を設定する

set API_URL=https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod
set USER_POOL_ID=ap-northeast-1_XXXXXXXX
set CLIENT_ID=XXXXXXXXXXXXXXXXXXXXXXXXXX

テスト1: テストユーザーを作成する

aws cognito-idp admin-create-user ^
  --user-pool-id %USER_POOL_ID% ^
  --username test@example.com ^
  --region ap-northeast-1

ユーザーが FORCE_CHANGE_PASSWORD 状態で作成されます。次のコマンドで永続パスワードを設定して CONFIRMED 状態にします。

aws cognito-idp admin-set-user-password ^
  --user-pool-id %USER_POOL_ID% ^
  --username test@example.com ^
  --password TestPass1 ^
  --permanent ^
  --region ap-northeast-1

レスポンスなし(エラーが出なければ成功)。

FORCE_CHANGE_PASSWORD 状態とは:
Cognito で管理者が作成したユーザーの初期状態です。この状態で initiate-auth を実行すると NEW_PASSWORD_REQUIRED チャレンジが返されてトークンが取得できません。--permanent フラグを付けて admin-set-user-password を実行することで CONFIRMED(認証済み)状態に変えられます。


テスト2: 公開エンドポイントのテスト(/hello)→ 200

curl %API_URL%/hello

期待するレスポンス:

{"message": "Hello! This is a public endpoint."}

テスト3: 未認証でプライベートエンドポイント(/profile)→ 401

curl %API_URL%/profile

期待するレスポンス(API Gateway が返す。Lambda は呼ばれていない):

{"message": "Unauthorized"}

テスト4: IDトークンを取得する

aws cognito-idp initiate-auth ^
  --auth-flow USER_PASSWORD_AUTH ^
  --auth-parameters "USERNAME=test@example.com,PASSWORD=TestPass1" ^
  --client-id %CLIENT_ID% ^
  --region ap-northeast-1

レスポンス例(抜粋):

{
    "AuthenticationResult": {
        "IdToken": "eyJra...",
        "AccessToken": "eyJra...",
        "RefreshToken": "eyJjb...",
        "ExpiresIn": 3600,
        "TokenType": "Bearer"
    }
}

IdToken の値をコピーします(次のテストで使用)。

IdToken と AccessToken の違い:

トークン用途JWT ペイロード
IdTokenユーザー認証(今回使用)email / sub など
AccessTokenCognito API へのアクセスsub / scope など

API Gateway の Cognito Authorizer に渡すのは IdToken。AccessToken を渡すと 403 になります。


テスト5: 認証ありでプライベートエンドポイント(/profile)→ 200

set ID_TOKEN=eyJra...(上記で取得した IdToken の値)
curl -H "Authorization: %ID_TOKEN%" %API_URL%/profile

重要: Bearer プレフィックスは不要です。raw JWT(eyJra...)をそのまま Authorization ヘッダーに指定してください。

期待するレスポンス:

{
  "message": "認証成功",
  "email": "test@example.com",
  "sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "token_use": "id"
}


Step 8: AWSコンソールで確認(任意)

SAM でデプロイしたリソースはコンソールでも確認できます。

  • API Gateway: APIs → cognito-api-lambda-stack → オーソライザー → CognitoAuthorizer が作成されていることを確認
  • Cognito: ユーザープール → 作成された User Pool → ユーザー一覧で test@example.com が「確認済み」になっていることを確認
  • Lambda: Lambda → 関数 → cognito-api-lambda-stack-ApiFunction-XXXX → 「モニタリング」タブ → テスト5実行後に呼び出し回数が増加していることを確認

Step 9: リソースの削除

課金を止めるために、ハンズオン完了後は必ずリソースを削除してください。

sam delete --stack-name cognito-api-lambda-stack --region ap-northeast-1

対話式で確認が入ります。両方 y で進めます。

Are you sure you want to delete the stack cognito-api-lambda-stack? [y/N]: y
Are you sure you want to delete the folder cognito-api-lambda-stack in S3? [y/N]: y

削除完了の確認

aws cloudformation describe-stacks --stack-name cognito-api-lambda-stack --region ap-northeast-1
An error occurred (ValidationError): Stack with id cognito-api-lambda-stack does not exist

このメッセージが表示されれば削除完了です。

sam delete で削除されるリソース一覧

リソース
Cognito User Pool(ユーザーデータも削除される)× 1
Cognito User Pool Client× 1
API Gateway REST API(ステージ含む)× 1
Lambda 関数× 1
IAM ロール× 1
Lambda デプロイパッケージ(S3)× 1
CloudWatch Logs ロググループ× 1

コンソール版との大きな違い(SAMのメリット):
コンソール版では API Gateway / Lambda / Cognito / IAM ロール / ロググループを個別に削除する必要があります。
SAMでは sam delete 1コマンドで全リソースを一括削除できます。


トラブルシューティング

症状原因対処
sam build が失敗するsrc/app.py が存在しないsrc/ フォルダに app.py があるか確認
sam deployCAPABILITY_IAM エラーsamconfig.toml の設定漏れcapabilities = "CAPABILITY_IAM" が含まれているか確認
sam deployMissing --stack-name エラーsamconfig.toml がない、または場所が違うcognito-api-lambda/ 直下に samconfig.toml を作成する
initiate-authNotAuthorizedException: Incorrect username or passwordユーザーが FORCE_CHANGE_PASSWORD 状態admin-set-user-password --permanent で CONFIRMED にする
initiate-authInvalidParameterExceptionALLOW_USER_PASSWORD_AUTH が無効sam deployUserPoolClient が正しく作成されているか確認
/profile に IDトークンを送っても 401Bearer TOKEN 形式で送っているraw JWT(eyJra...)をそのまま Authorization ヘッダーに指定する
/profile に IDトークンを送っても 403IDトークンが期限切れ(1時間で失効)initiate-auth で新しいトークンを取得し直す
/profile に IDトークンを送っても 403AccessToken を使っている(IdToken ではない)AuthenticationResult.IdToken を使用しているか確認
Lambda の claims が空AccessToken を使っているIdToken を Authorization ヘッダーに指定する
テストで executionArn が同じ名前で失敗するadmin-create-userUsernameExistsExceptionすでに作成済みのためスキップして問題なし

まとめ

今回のハンズオンで実現したこと:

確認項目内容
SAM での Cognito 定義UserPool + UserPoolClient(シークレットなし)を template.yaml で管理
DefaultAuthorizer全エンドポイントに Cognito 認証を一括適用。Authorizer: NONE で /hello を除外
JWT 認証フローCLI で IDトークンを取得 → Authorization ヘッダーに指定 → claims を返す
一括削除sam delete で Cognito / API Gateway / Lambda を全てクリーンアップ

SAMのメリットを実感できたポイント

  • GenerateSecret: false + ExplicitAuthFlows でシークレットなしクライアントを定義(コンソール版では SPA タイプのクライアントを別途手動作成)
  • DefaultAuthorizer の1行で全エンドポイントに Cognito 認証を適用(コンソール版では各エンドポイントに手動設定)
  • UserPoolArn: !GetAtt UserPool.Arn で Cognito Authorizer と User Pool を自動連携(コンソール版では手動選択 + トークンのソースを手入力)

コンソール版と比較してみる

SAMが裏で何をやっているか、同じ構成をAWSコンソールのみで構築する手順をまとめました。Cognitoの新UIで変わった操作(「アプリケーションを作成」からのウィザード、シークレットなしクライアントの作成方法、Authorizer のトークンソース設定)など、コンソール版ならではのつまずきポイントを解説しています。


関連記事

Amazon検索[本 AWS 開発]

コメント