Cognito ユーザープールとIDプールを使用した認証ユーザーによるS3ファイルアップロードを試してみた

Cognito ユーザープールとIDプールを使用した認証ユーザーによるS3ファイルアップロードを試してみた

Clock Icon2024.11.13

はじめに

本記事では、Amazon Cognitoユーザープールを使用したユーザー認証の実装方法と、とCognito IDプールを使用した認証済みユーザーによるS3へのファイルアップロード方法について解説します。

本実装で使用するAWSサービスと各役割は次のとおりです。

  • Amazon Cognito ユーザープール:ユーザー認証基盤
  • Amazon Cognito ID プール:一時的なAWS認証情報の発行
  • Amazon S3:認証されたユーザーがファイルアップロードするストレージ
  • AWS IAM:Cognito ID プールで設定したIAMロールを利用し、認証されたユーザーがファイルアップロードする権限を付与

認証からファイルアップロードまでの処理フローは以下の通りです。

  1. ユーザープールを利用しユーザー認証をします。
  2. 認証が成功すると、クライアントへIDトークンが払い出されます。
  3. IDトークンをCognito IDプールに渡すと、Cognito IDプールのIAMロールに割り当てられた一時的なAWS認証情報を受け取ります。
  4. 一時的なAWS認証情報を利用し、クライアントはS3へファイルをアップロードします。

scenario-cup-cib
参考構成図
引用元: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です。

現時点では確認ステータスは、未確認です。
cm-hirai-screenshot 2024-11-07 16.43.25

管理者として、ユーザーのサインアップを確認(承認)します。
このコマンドを実行すると、指定したユーザーのアカウントが確認済み状態となり、ログインが可能になります。
メールアドレスは先程設定した値に変更ください。

$ aws cognito-idp admin-confirm-sign-up \
  --user-pool-id ${USER_POOL_ID} \
  --username example@example.com

実行後、確認ステータスは、確認済みになりました。これでログインできます。

cm-hirai-screenshot 2024-11-07 16.47.41

以下のコマンドを実行して、ユーザーの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も指定する必要があります。
指定方法は、以下の記事をご参照ください。
https://zenn.dev/ishimura/articles/2445a4c42a9d4c

取得したIDトークンを環境変数に設定します。

$ ID_TOKEN="zzzzzzzzzzzzzzzzzzzzzzz"

IDプールに対して、IDトークンからIdentityIdを取得後、さらにIDプールに対して、IDトークンとIdentityIdを使用して一時的なAWS認証情報を取得します。

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/authentication-flow.html

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}/)配下のみにアクセスが制限されており、それ以外の場所へのアクセスは拒否されます。
これにより、ユーザー間のデータ分離が実現できます。

リソースの削除

作成したリソースを以下の順序で削除します。

  1. 一時的なAWS認証情報の削除
  2. S3オブジェクトの削除
  3. IAMロールとポリシーの削除
  4. Cognito関連リソースの削除
  5. ローカル環境のクリーンアップ

一時的な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

以上でリソースの削除は完了です。

まとめ

本記事では、以下の内容について実装方法を解説しました

  1. Amazon Cognitoユーザープールを使用したユーザー認証基盤の構築
  2. Amazon Cognito IDプールを使用した認証済みユーザーへの一時的なAWS認証情報の提供
  3. S3バケットへのセキュアなファイルアップロード
  4. ユーザーごとのデータ分離の実現

本実装はテスト目的の基本的な設定ですが、実際の開発では、MFAの有効化やクライアントシークレットの設定など、追加のセキュリティ対策を検討してください。

参考

https://dev.classmethod.jp/articles/get-aws-temporary-security-credentials-with-cognito-id-pool-by-aws-cli/

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.