はじめに
AWS SAM(Serverless Application Model)を使って、Cognito認証付きのサーバーレスアプリケーションを構築してみました。
昨夜のうちに検証して投稿しようと頑張っていたのですが、検証でうまくいかない部分があり、最終的にまとめ終わったのは翌朝になってしまいました…!(思った以上に手こずりました...Cognito久しぶりだったから)
今回は、S3によるWebホスティング、CloudFrontによるHTTPS配信、API Gateway + Lambda + DynamoDB のバックエンド、そしてCognitoによるユーザー認証を統合した構成です。
AWS SAM 本当に便利で連日、感動中です...!!
書こうと思ったきっかけ
個人的に「SAMでCognitoを使ってみたい!!」という気持ちがずっとありました。普段は手動でCognitoを設定していましたが、インフラをすべてコード化することで理解が深まると思い、試してみることにしました...!
前回の記事の追加検証になります!
構成図はこんな感じになります(CloudFrontとCognitoを追加)
※内容に不備などがございましたら、お手数ですが優しくご指摘いただけますと幸いです。
ディレクトリ構成
本構成のディレクトリ構成は以下のようになっています。
sam-cognito-api-example/
├── template.yaml # SAMテンプレート(Cognito追加あり)
├── samconfig.toml # デプロイ設定ファイル
├── hello_world/
│ ├── app.py # Lambda関数(DynamoDB読み取り)
│ └── __init__.py # 空でOK
└── frontend/
├── index.html # Cognitoログインボタンあり
├── callback.html # トークン取得・API呼び出し
└── script.js # トークン処理&APIアクセス
※今回は、フロントエンドやバックエンドのコードには触れず、SAM テンプレートに焦点を当てて、デプロイ手順を中心に解説していきます。
テンプレート内容
今回の構成に含まれるリソースは以下の通りです:
-
S3バケット:
index.html
をホスティングする静的ウェブサイト -
CloudFront:HTTPSでの配信対応(
redirect-to-https
) -
DynamoDB:
ItemsTable
に保存されたデータをAPI経由で取得 - Cognito User Pool / Client / Domain:ログイン機能を提供
-
API Gateway + Lambda:APIとして
/items
エンドポイントを提供し、DynamoDBの内容を返す - Cognito Authorizer:API Gateway によるCognito認証
このテンプレートでは、CloudFormationテンプレートでCognito認証付きの構成全体を自動展開できるようになっています。
ユーザーは、CloudFront経由で配信されるWebアプリにアクセスし、Cognitoログイン後にAPIを呼び出してDynamoDBのデータを取得するという流れです。
以下は、実際に使用したテンプレートコードです:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: API Gateway + Lambda + DynamoDB + S3 + Cognito Auth
Globals:
Function:
Timeout: 10
Resources:
WebHostingBucket:
Type: AWS::S3::Bucket
Properties:
WebsiteConfiguration:
IndexDocument: index.html
OwnershipControls:
Rules:
- ObjectOwnership: ObjectWriter
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref WebHostingBucket
PolicyDocument:
Statement:
- Action: s3:GetObject
Effect: Allow
Resource: !Sub "${WebHostingBucket.Arn}/*"
Principal: "*"
ItemsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ItemsTable
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
MyUserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: MyUserPool
AutoVerifiedAttributes:
- email
UsernameAttributes:
- email
# CloudFront Distribution for HTTPS
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName: !Select [2, !Split ["/", !GetAtt WebHostingBucket.WebsiteURL]]
Id: S3Origin
CustomOriginConfig:
HTTPPort: 80
OriginProtocolPolicy: http-only
Enabled: true
DefaultRootObject: index.html
DefaultCacheBehavior:
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
AllowedMethods: [GET, HEAD]
CachedMethods: [GET, HEAD]
ForwardedValues:
QueryString: false
Cookies:
Forward: none
Comment: !Sub "${AWS::StackName} CloudFront Distribution"
MyUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: MyUserPoolClient
UserPoolId: !Ref MyUserPool
GenerateSecret: false
AllowedOAuthFlowsUserPoolClient: true
AllowedOAuthFlows:
- implicit
AllowedOAuthScopes:
- email
- openid
CallbackURLs:
- !Sub "https://${CloudFrontDistribution.DomainName}/callback.html"
SupportedIdentityProviders:
- COGNITO
# Cognito User Pool Domain(標準ドメイン)
MyUserPoolDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
Domain: !Sub "myapp-${AWS::StackName}-${AWS::AccountId}"
UserPoolId: !Ref MyUserPool
MyApi:
Type: AWS::Serverless::Api
Properties:
Name: MyApi
StageName: Prod
Cors:
AllowMethods: "'GET,OPTIONS'"
AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
AllowOrigin: "'*'"
Auth:
DefaultAuthorizer: CognitoAuthorizer
AddDefaultAuthorizerToCorsPreflight: false
Authorizers:
CognitoAuthorizer:
UserPoolArn: !GetAtt MyUserPool.Arn
GetItemsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.12
Environment:
Variables:
TABLE_NAME: !Ref ItemsTable
Policies:
- DynamoDBReadPolicy:
TableName: !Ref ItemsTable
Events:
GetItemsApi:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /items
Method: get
OptionsItemsApi:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /items
Method: options
Outputs:
WebURL:
Value: !GetAtt WebHostingBucket.WebsiteURL
Description: "S3 Static Website URL (HTTP)"
CloudFrontURL:
Value: !Sub "https://${CloudFrontDistribution.DomainName}"
Description: "CloudFront Distribution URL (HTTPS)"
ApiURL:
Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/items"
UserPoolId:
Value: !Ref MyUserPool
UserPoolClientId:
Value: !Ref MyUserPoolClient
CognitoDomainURL:
Value: !Sub "https://${MyUserPoolDomain}.auth.${AWS::Region}.amazoncognito.com"
Description: "Cognito認証用ドメインURL"
CallbackURL:
Value: !Sub "https://${CloudFrontDistribution.DomainName}/callback.html"
Description: "Cognito Callback URL (HTTPS)"
AWS環境にデプロイしてみた
上記の構成を sam deploy
でデプロイしたところ、以下のような流れでサーバーレスアプリが立ち上がりました。
-
sam build && sam deploy
で全リソースを一括作成
- S3バケットに
frontend/index.html
などをアップロード
→実際に使ったコマンド
aws s3 cp frontend/ s3://バケット名 --recursive
問題なくアップロードされていることが確認できました!
- CloudFrontのURLにアクセスすると、Cognitoログインボタン付きのトップページが表示される
ログイン画面
すでにCognito側でユーザーを作成済みとなりますので、そのユーザーでログインを試していきます。
問題なくログインできることが確認できましたので、今回の検証はこれで以上とします!
CognitoのOAuth連携やトークンの扱いも callback.html
+ script.js
で処理しており、かなり実践的な構成になったと思います...!(少し難しかったです!)
まとめ
ここまでお読みいただき、ありがとうございました!
AWS SAMを使うことで、サーバーレスアプリの複雑な構成もテンプレート1枚で管理でき、Cognito認証との統合もスムーズに行えました。
今後はこの構成をベースに、ユーザー登録・データ投稿機能の追加などを行っていきたいと考えています。
Cognito連携の自動化に挑戦したい方にとっても、良い学びの機会になる構成だと思います...!!
不要になったリソースは以下のコマンドで削除できますので、適宜削除しておくことをおすすめします。
aws cloudformation delete-stack --stack-name sam-app
今後も応用的な構成に対応できるよう、引き続きキャッチアップを進めていきたいと思います...!
参考文献