はじめに
「認証付き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::UserPoolとAWS::Cognito::UserPoolClientの SAM 定義DefaultAuthorizerを使ったエンドポイント単位の認証制御(1行で全エンドポイントに認証を適用)Authorizer: NONEで特定エンドポイントだけ認証を除外するパターン- CLI でのテスト実行(ユーザー作成 → IDトークン取得 → 認証済みリクエスト)
このハンズオンの特徴:
sam build+sam deployの 2コマンドで全リソースをデプロイ- Cognito User Pool / User Pool Client / API Gateway / Lambda / IAM ロールを
template.yaml1ファイルで管理 sam deleteで全リソースを一括削除(コンソール版では各リソースを個別に削除する必要がある)
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 Pool | Cognito のユーザーディレクトリ。ユーザー登録・認証・JWT 発行を担う |
| User Pool Client | アプリが User Pool に接続するための設定。Client ID を持つ |
| IDトークン | ログイン成功時に Cognito が発行する JWT。email / sub などのユーザー情報を含む |
| Cognito Authorizer | API Gateway が IDトークンを検証する認可機能。Lambda コードが不要で動作する |
| claims | JWT ペイロードに含まれる情報(email / sub / token_use など) |
| DefaultAuthorizer | 全エンドポイントに一括でオーソライザーを適用する SAM の設定。個別に Authorizer: NONE で除外できる |
前提条件
| ツール | 確認コマンド | 最低バージョン目安 |
|---|---|---|
| AWS CLI v2 | aws --version | 2.x |
| AWS SAM CLI | sam --version | 1.x |
| Python 3.12 | python --version | 3.12 |
aws sts get-caller-identityアカウントIDが表示されれば認証設定済みです。
使用するAWSサービス
| サービス | 役割 | 料金 |
|---|---|---|
| Amazon Cognito | ユーザー管理・認証・JWT発行 | 月5万MAUまで無料 |
| API Gateway | REST API のエンドポイント管理・認可 | 月100万リクエストまで無料(無料期間12ヶ月) |
| Lambda | APIのビジネスロジック(1関数) | 月100万リクエスト・400,000 GB-秒まで無料 |
| IAM | Lambda の実行権限管理 | 無料 |
| S3 | SAM デプロイパッケージ置き場 | 少量のため実質無料 |
| CloudWatch Logs | Lambda の実行ログ | 月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_AUTHAPI 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: false | CLI テスト用にシークレットなしのクライアントを定義。コンソール版では SPA タイプのクライアントを手動作成 |
DefaultAuthorizer | 全エンドポイントに Cognito 認証をデフォルト適用。コンソール版ではエンドポイントごとに手動設定 |
Authorizer: NONE | /hello など特定エンドポイントの認証を除外。コンソール版では /hello に Authorizer を設定しないことに対応 |
UserPoolArn: !GetAtt UserPool.Arn | Cognito 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 コードは共通です。コンソール版では手順とあわせてコードを全文掲載しています。
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]: yy を入力して進めます。数分でデプロイが完了します。
デプロイ完了の確認
ターミナルに 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 自動生成ログ × 1Step 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 など AccessToken Cognito 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-1An 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 delete1コマンドで全リソースを一括削除できます。
トラブルシューティング
| 症状 | 原因 | 対処 |
|---|---|---|
sam build が失敗する | src/app.py が存在しない | src/ フォルダに app.py があるか確認 |
sam deploy が CAPABILITY_IAM エラー | samconfig.toml の設定漏れ | capabilities = "CAPABILITY_IAM" が含まれているか確認 |
sam deploy が Missing --stack-name エラー | samconfig.toml がない、または場所が違う | cognito-api-lambda/ 直下に samconfig.toml を作成する |
initiate-auth で NotAuthorizedException: Incorrect username or password | ユーザーが FORCE_CHANGE_PASSWORD 状態 | admin-set-user-password --permanent で CONFIRMED にする |
initiate-auth で InvalidParameterException | ALLOW_USER_PASSWORD_AUTH が無効 | sam deploy で UserPoolClient が正しく作成されているか確認 |
/profile に IDトークンを送っても 401 | Bearer TOKEN 形式で送っている | raw JWT(eyJra...)をそのまま Authorization ヘッダーに指定する |
/profile に IDトークンを送っても 403 | IDトークンが期限切れ(1時間で失効) | initiate-auth で新しいトークンを取得し直す |
/profile に IDトークンを送っても 403 | AccessToken を使っている(IdToken ではない) | AuthenticationResult.IdToken を使用しているか確認 |
Lambda の claims が空 | AccessToken を使っている | IdToken を Authorization ヘッダーに指定する |
テストで executionArn が同じ名前で失敗する | admin-create-user で UsernameExistsException | すでに作成済みのためスキップして問題なし |
まとめ
今回のハンズオンで実現したこと:
| 確認項目 | 内容 |
|---|---|
| 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 のトークンソース設定)など、コンソール版ならではのつまずきポイントを解説しています。
コメント