Cognito ユーザープールとIDプールを使用した認証ユーザーによるS3ファイルアップロードを試してみた
はじめに
本記事では、Amazon Cognitoユーザープールを使用したユーザー認証の実装方法と、とCognito IDプールを使用した認証済みユーザーによるS3へのファイルアップロード方法について解説します。
本実装で使用するAWSサービスと各役割は次のとおりです。
- Amazon Cognito ユーザープール:ユーザー認証基盤
- Amazon Cognito ID プール:一時的なAWS認証情報の発行
- Amazon S3:認証されたユーザーがファイルアップロードするストレージ
- AWS IAM:Cognito ID プールで設定したIAMロールを利用し、認証されたユーザーがファイルアップロードする権限を付与
認証からファイルアップロードまでの処理フローは以下の通りです。
- ユーザープールを利用しユーザー認証をします。
- 認証が成功すると、クライアントへIDトークンが払い出されます。
- IDトークンをCognito IDプールに渡すと、Cognito IDプールのIAMロールに割り当てられた一時的なAWS認証情報を受け取ります。
- 一時的なAWS認証情報を利用し、クライアントはS3へファイルをアップロードします。
参考構成図
引用元:https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-scenarios.html
環境構築手順
以降の手順は、AWS CloudShellを使用してAWS CLIコマンドを実行します。
AWS CloudShellは、ブラウザベースのシェル環境で、AWSの認証情報が事前に設定されているため、すぐにAWS CLIコマンドを実行できます。
Cognito ユーザープール作成
まず、ユーザー認証を管理するため、Cognitoユーザープールを作成します。
このコマンドで設定する主なパラメータは以下の通りです。
- ユーザープール名:TestUserPool
- パスワードポリシー:8文字以上、大文字小文字数字記号を含む
- ユーザー名属性:メールアドレスを使用
今回は、検証のため、MFA(Multi-Factor Authentication)は有効化していません。
$ aws cognito-idp create-user-pool \
--pool-name "TestUserPool" \
--policies '{"PasswordPolicy":{"MinimumLength":8,"RequireUppercase":true,"RequireLowercase":true,"RequireNumbers":true,"RequireSymbols":true}}' \
--username-attributes email
{
"UserPool": {
"Id": "ap-northeast-1_ibnFzeUb9",
以降のコマンドで使用するため、生成されたユーザープールIDを環境変数として設定します。
実際のIDは上記のコマンド実行結果で表示されたものを使用してください。
$ USER_POOL_ID="ap-northeast-1_ibnFzeUb9"
続いて、Cognito ユーザープールのアプリケーションクライアントを作成します。
今回は、ユーザー名とパスワードによる認証を利用するためALLOW_USER_PASSWORD_AUTH
は必須です。
$ aws cognito-idp create-user-pool-client \
--user-pool-id ${USER_POOL_ID} \
--client-name "TestClient" \
--explicit-auth-flows ALLOW_USER_PASSWORD_AUTH ALLOW_REFRESH_TOKEN_AUTH ALLOW_USER_SRP_AUTH
{
"UserPoolClient": {
"UserPoolId": "ap-northeast-1_ibnFzeUb9",
"ClientName": "TestClient",
"ClientId": "7e4g7ksoahjivvk6av4c2iun4d",
作成されたクライアントIDを環境変数に設定します。
$ CLIENT_ID="7e4g7ksoahjivvk6av4c2iun4d"
Cognito IDプール作成
認証されたユーザーに一時的なAWS認証情報を提供できるようCognito IDプールを作成します。
このコマンドでは以下の設定を行っています
- Cognito IDプール名:TestIdentityPool
- IDプールと連携する認証プロバイダー:作成したCognitoユーザープール
- 未認証ユーザーは一時的なAWS認証情報を提供しないよう設定。認証済みユーザーのみ提供する
$ aws cognito-identity create-identity-pool \
--identity-pool-name "TestIdentityPool" \
--no-allow-unauthenticated-identities \
--cognito-identity-providers "[{\"ProviderName\":\"cognito-idp.ap-northeast-1.amazonaws.com/${USER_POOL_ID}\",\"ClientId\":\"${CLIENT_ID}\",\"ServerSideTokenCheck\":false}]"
{
"IdentityPoolId": "ap-northeast-1:8edab2a4-748e-4873-ae94-c1c7d4b43120",
"IdentityPoolName": "TestIdentityPool",
"AllowUnauthenticatedIdentities": false,
"CognitoIdentityProviders": [
{
"ProviderName": "cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_ibnFzeUb9",
"ClientId": "7e4g7ksoahjivvk6av4c2iun4d",
"ServerSideTokenCheck": false
}
],
"IdentityPoolTags": {}
}
作成されたIDプールのIDを環境変数に設定します。
$ IDENTITY_POOL_ID="ap-northeast-1:8edab2a4-748e-4873-ae94-c1c7d4b43120"
IAMロールとポリシーの設定
ファイルアップロード用のS3バケット名とAWSアカウントIDを環境変数として設定します。これらの値は後続のコマンドで使用します。
$ S3_BUCKET_NAME="xxxxxxxxxxxx"
$ ACCOUNT_ID="xxxxxxxxxxxx" # あなたのAWSアカウントID
認証されたユーザーがS3バケットにファイルをアップロード許可するためのIAMロールを作成します。
$ aws iam create-role \
--role-name CognitoAuthRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "'${IDENTITY_POOL_ID}'"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}]
}'
S3アクセス用IAMポリシーを作成し、作成したIAMロールにアタッチします。
このポリシーにより、認証されたユーザーは以下のアクセス権限が付与されます。
- S3バケット内のオブジェクト一覧を閲覧可能
- ユーザー専用のプレフィックス配下にのみファイルの読み書きが可能
${cognito-identity.amazonaws.com:sub}
: Cognito認証時に割り当てられるユーザー固有のID
- 他のユーザーのプレフィックス配下にはアクセス不可
$ aws iam put-role-policy \
--role-name CognitoAuthRole \
--policy-name S3Access \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::'${S3_BUCKET_NAME}'"
]
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::'${S3_BUCKET_NAME}'/${cognito-identity.amazonaws.com:sub}",
"arn:aws:s3:::'${S3_BUCKET_NAME}'/${cognito-identity.amazonaws.com:sub}/*"
]
}
]
}'
Cognito IDプールにIAMロールを関連付けます。
$ aws cognito-identity set-identity-pool-roles \
--identity-pool-id ${IDENTITY_POOL_ID} \
--roles authenticated=arn:aws:iam::${ACCOUNT_ID}:role/CognitoAuthRole
動作確認
ユーザー作成と認証
ユーザープールにテストユーザーを作成します。
以下のコマンドを実行する際は、メールアドレスとパスワードを個々の適切な値に変更してください。
$ aws cognito-idp sign-up \
--client-id ${CLIENT_ID} \
--username example@example.com \
--password TestPassword123!
{
"UserConfirmed": false,
"UserSub": "47f47a88-40d1-7064-dfe8-9d0e2cd38950"
}
47f47a88-40d1-7064-dfe8-9d0e2cd38950
は、ユーザーIDです。
現時点では確認ステータスは、未確認
です。
管理者として、ユーザーのサインアップを確認(承認)します。
このコマンドを実行すると、指定したユーザーのアカウントが確認済み状態となり、ログインが可能になります。
メールアドレスは先程設定した値に変更ください。
$ aws cognito-idp admin-confirm-sign-up \
--user-pool-id ${USER_POOL_ID} \
--username example@example.com
実行後、確認ステータスは、確認済み
になりました。これでログインできます。
以下のコマンドを実行して、ユーザーのIDトークンを取得します。
認証が成功すると、以下の情報が返されます
- AccessToken: API呼び出しに使用する認証トークン
- ExpiresIn: トークンの有効期限(秒)
- TokenType: トークンの種類(Bearer)
- RefreshToken: アクセストークンの更新に使用
- IdToken: ID情報を含むトークン(Cognito IDプールで使用)
$ aws cognito-idp initiate-auth \
--auth-flow USER_PASSWORD_AUTH \
--client-id ${CLIENT_ID} \
--auth-parameters '{
"USERNAME": "example@example.com",
"PASSWORD": "TestPassword123!"
}'
{
"ChallengeParameters": {},
"AuthenticationResult": {
"AccessToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"ExpiresIn": 3600,
"TokenType": "Bearer",
"RefreshToken": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"IdToken": "zzzzzzzzzzzzzzzzzzzzzzz"
}
}
今回は、アプリケーションクライアントはクライアントシークレットを生成していません。
もし、クライアントシークレットを生成されていれば、SECRET_HASHも指定する必要があります。
指定方法は、以下の記事をご参照ください。
取得したIDトークンを環境変数に設定します。
$ ID_TOKEN="zzzzzzzzzzzzzzzzzzzzzzz"
IDプールに対して、IDトークンからIdentityIdを取得後、さらにIDプールに対して、IDトークンとIdentityIdを使用して一時的なAWS認証情報を取得します。
IdentityIdは、Cognito IDプールが各ユーザーに割り当てる一意の識別子です。リージョン名:ユーザーID(Sub)
です。
マネジメントコンソール画面からもユーザーIDは確認できるため、IdentityIdは取得できますが、今回はコマンドで実行してみます。
$ aws cognito-identity get-id \
--identity-pool-id ${IDENTITY_POOL_ID} \
--logins cognito-idp.ap-northeast-1.amazonaws.com/${USER_POOL_ID}=${ID_TOKEN}
{
"IdentityId": "ap-northeast-1:47f47a88-40d1-7064-dfe8-9d0e2cd38950"
}
取得したIdentityIdを環境変数に設定します。
$ IDENTITY_ID="ap-northeast-1:47f47a88-40d1-7064-dfe8-9d0e2cd38950"
IDトークンとIdentityIdを使用して一時的なAWS認証情報を取得します。
この認証情報を利用することで、S3バケットに対してアクセスが可能になります。
$ aws cognito-identity get-credentials-for-identity \
--identity-id ${IDENTITY_ID} \
--logins cognito-idp.ap-northeast-1.amazonaws.com/${USER_POOL_ID}=${ID_TOKEN}
{
"IdentityId": "ap-northeast-1:47f47a88-40d1-7064-dfe8-9d0e2cd38950",
"Credentials": {
"AccessKeyId": "AAA",
"SecretKey": "BBB",
"SessionToken": "CCC",
"Expiration": "2024-10-31T02:58:52+00:00"
}
}
一時的なAWS認証情報を環境変数として設定します。
$ export AWS_ACCESS_KEY_ID="AAA"
export AWS_SECRET_ACCESS_KEY="BBB"
export AWS_SESSION_TOKEN="CCC"
S3へのファイルアップロード
AWS CloudShell上で、テストファイル作成し、S3にアップロードします。
$ echo "Hello, World!" > test.txt
$ aws s3 cp test.txt s3://${S3_BUCKET_NAME}/${IDENTITY_ID}/test.txt
upload: ./test.txt to s3://cm-hirai-cognito/ap-northeast-1:46ab9a12-3bda-c346-a049-bc5fcf6e801c/test.txt
# アップロードの確認
$ aws s3 ls s3://${S3_BUCKET_NAME}/${IDENTITY_ID}/
2024-11-07 08:06:53 14 test.txt
ユーザー名のプレフィックス配下でのみアップロードできることを確認します。
$ aws s3 cp test.txt s3://${S3_BUCKET_NAME}/test.txt
upload failed: ./test.txt to s3://cm-hirai-cognito/test.txt An error occurred (AccessDenied) when calling the PutObject operation: User: arn:aws:sts::123456789012:assumed-role/CognitoAuthRole/CognitoIdentityCredentials is not authorized to perform: s3:PutObject on resource: "arn:aws:s3:::cm-hirai-cognito/test.txt" because no identity-based policy allows the s3:PutObject action
このアクセス拒否エラーは、セキュリティ設定が正しく機能していることを示しています。
IAMポリシーにより、各ユーザーは自身のディレクトリ(${IDENTITY_ID}/)配下のみにアクセスが制限されており、それ以外の場所へのアクセスは拒否されます。
これにより、ユーザー間のデータ分離が実現できます。
リソースの削除
作成したリソースを以下の順序で削除します。
- 一時的なAWS認証情報の削除
- S3オブジェクトの削除
- IAMロールとポリシーの削除
- Cognito関連リソースの削除
- ローカル環境のクリーンアップ
一時的なAWS認証情報の環境変数をクリアします。
$ unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
unset AWS_SESSION_TOKEN
元の認証情報を使用していることを確認
$ aws sts get-caller-identity
S3バケットにアップロードしたファイル削除します。
$ aws s3 rm s3://${S3_BUCKET_NAME}/${IDENTITY_ID}/test.txt
# 必要に応じてユーザーの`${IDENTITY_ID}`フォルダ(プレフィックス)ごとを削除します。
$ aws s3 rm s3://${S3_BUCKET_NAME}/${IDENTITY_ID}/ --recursive
IDプールに関連付けられたIAMロールのポリシーとIAMロールを削除後、Cognito IDプールを削除します。
$ aws iam delete-role-policy \
--role-name CognitoAuthRole \
--policy-name S3Access
$ aws iam delete-role \
--role-name CognitoAuthRole
$ aws cognito-identity delete-identity-pool \
--identity-pool-id ${IDENTITY_POOL_ID}
ユーザープールクライアント削除後、ユーザープールを削除します。
$ aws cognito-idp delete-user-pool-client \
--user-pool-id ${USER_POOL_ID} \
--client-id ${CLIENT_ID}
$ aws cognito-idp delete-user-pool \
--user-pool-id ${USER_POOL_ID}
CloudShell上で作成したテストファイルを削除します。
$ rm test.txt
環境変数を削除します。
$ unset USER_POOL_ID
unset CLIENT_ID
unset IDENTITY_POOL_ID
unset S3_BUCKET_NAME
unset IDENTITY_ID
unset ACCOUNT_ID
以上でリソースの削除は完了です。
まとめ
本記事では、以下の内容について実装方法を解説しました
- Amazon Cognitoユーザープールを使用したユーザー認証基盤の構築
- Amazon Cognito IDプールを使用した認証済みユーザーへの一時的なAWS認証情報の提供
- S3バケットへのセキュアなファイルアップロード
- ユーザーごとのデータ分離の実現
本実装はテスト目的の基本的な設定ですが、実際の開発では、MFAの有効化やクライアントシークレットの設定など、追加のセキュリティ対策を検討してください。
参考