AWS Amplify Gen2でS3のユーザー別フォルダアクセス制御を実装する
はじめに
こんにちは、スーパーマーケットが大好きなコンサルティング部の神野です。
Webアプリケーションでユーザーごとにファイルを管理したい場合、S3バケットへのアクセス制御をどう実装するか悩むことがありますよね。アプリケーション側だけでなくインフラ(IAM)側も権限制御したい場合は、Cognitoで認証したユーザーに対して、個人フォルダやグループフォルダを作り、適切なアクセス権限を設定するのは意外と手間がかかります。下記記事のようにユーザーのIDとIAMの権限を連携して設定を行い権限分離をするイメージです。
Amplify Gen2でも同じように設定するかーと公式ドキュメントを確認していたら、この要件をシンプルに実装できる機能があることがわかりました!
実際に試してみたので紹介させていただきます。
サンプルアプリケーションも作成しましたので、実際に動かす際は
こちらのリポジトリをご参照ください。
今回やりたいことのイメージは下記になります。

Amplify Gen2のストレージ認可
Amplify Gen2では、宣言的な方法でストレージの認可ルールを定義できます。
以下のようなアクセスパターンを簡単に実装可能です。
- public: 誰でも読める、認証済みユーザーは書き込める
- protected: 誰でも読めるが、所有者のみ変更・削除可能
- private: 所有者のみアクセス可能
- グループベース: Cognitoグループに基づいたアクセス制御
公式ドキュメントのAccess definition rulesに詳しい説明がありますが、今回は実際に使ってみた感想を含めてご紹介します。
今回は上記のアクセスパターンをイメージして実装してみます。
実装してみる
それでは、実際にAmplify Gen2でストレージ認可を設定していきましょう!
ストレージ定義の作成
Amplify Gen2では、amplify/storage/resource.ts(またはプロジェクト構成に応じた場所)にストレージの定義を記述します。
import { defineStorage } from "@aws-amplify/backend";
/**
* Define and configure your storage resource
* @see https://docs.amplify.aws/gen2/build-a-backend/storage/
*/
export const storage = defineStorage({
name: "amplifyS3Gen2Storage",
access: (allow) => ({
// Public: anyone can read, authenticated users can read/write/delete
"public/*": [
allow.guest.to(["read"]),
allow.authenticated.to(["read", "write", "delete"]),
],
// Protected: owner can read/write/delete, authenticated users can read
// 'read' includes both 'get' (download) and 'list' (browse) permissions
"protected/{entity_id}/*": [
allow.authenticated.to(["read"]),
allow.entity("identity").to(["read", "write", "delete"]),
],
// Private: only the owner can access
"private/{entity_id}/*": [
allow.entity("identity").to(["read", "write", "delete"]),
],
}),
});
シンプルでいいですね!宣言的に書けるので、誰が何にアクセスできるのかがわかりやすいです。
それぞれの設計の意図を解説していきます。
各アクセスパターンの解説
まず、publicフォルダについて説明します。
'public/*': [
allow.guest.to(['read']),
allow.authenticated.to(['read', 'write', 'delete'])
]
ゲストユーザー(未認証)でも読み取り可能で、認証済みユーザーは読み書き・削除が可能です。
画像やドキュメントなど、一般公開したいファイルの配置に使用するイメージです。
次に、protectedフォルダです。
'protected/{entity_id}/*': [
allow.authenticated.to(['read']),
allow.entity('identity').to(['read', 'write', 'delete'])
]
{entity_id}トークンを使うことで、ユーザーごとのフォルダを自動的に作成できます。内部的には、ユーザーごとの一意なIDが使用され、実際のS3バケット内では以下のようなパス構造になります。動的にパスを定義できるのは嬉しいポイントかと思います。
s3://bucket-name/protected/us-west-2:0800230f-0a50-c165-4067-f0e446028bd4/file.png
認証されたユーザーは誰でもファイルを読むことができますが、書き込み・削除は所有者(identity)のみ可能です。
限られたメンバーに公開はしたいが編集は本人のみに制限したい場合などに利用するイメージです。
続いて、privateフォルダです。
'private/{entity_id}/*': [
allow.entity('identity').to(['read', 'write', 'delete'])
]
所有者のみがアクセス可能な完全プライベートなフォルダです。
個人の文書や設定ファイルなど、他のユーザーに見せたくないファイルの保存をする際に活用するイメージです。
最後に、グループベースのアクセス制御について説明します。
こちらはサンプルには実装していないので共有のみになります。
'team/group-a/*': [
allow.groups(['Admins', 'Managers']).to(['read', 'write', 'delete']),
allow.groups(['Users']).to(['read'])
]
Cognitoのユーザーグループと連携したアクセス制御も可能です。
AdminsやManagersグループのユーザーは読み書き・削除が可能で、Usersグループのユーザーは読み取りのみ可能な作りもできます。ただ注意なのがentity_idのようなグループ名に応じた動的なパス設計はできません。あくまで固定のパスに対して指定のグループの権限を付与するといった作りになります。
実際に生成されるIAMポリシーを確認してみる
宣言的に書いた定義が、実際にどのようなIAMポリシーに変換されているか気になりますよね。デプロイ後にCognito Identity Poolの認証済みロールに付与されたポリシーを確認してみました。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::amplify-xxxxx-amplifys3gen2storagebuck-xxxxx/public/*",
"arn:aws:s3:::amplify-xxxxx-amplifys3gen2storagebuck-xxxxx/protected/*",
"arn:aws:s3:::amplify-xxxxx-amplifys3gen2storagebuck-xxxxx/private/${cognito-identity.amazonaws.com:sub}/*"
],
"Effect": "Allow"
},
{
"Condition": {
"StringLike": {
"s3:prefix": [
"public/*",
"public/",
"protected/*",
"protected/",
"private/${cognito-identity.amazonaws.com:sub}/*",
"private/${cognito-identity.amazonaws.com:sub}/"
]
}
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::amplify-xxxxx-amplifys3gen2storagebuck-xxxxx",
"Effect": "Allow"
},
{
"Action": "s3:PutObject",
"Resource": [
"arn:aws:s3:::amplify-xxxxx-amplifys3gen2storagebuck-xxxxx/public/*",
"arn:aws:s3:::amplify-xxxxx-amplifys3gen2storagebuck-xxxxx/protected/${cognito-identity.amazonaws.com:sub}/*",
"arn:aws:s3:::amplify-xxxxx-amplifys3gen2storagebuck-xxxxx/private/${cognito-identity.amazonaws.com:sub}/*"
],
"Effect": "Allow"
},
{
"Action": "s3:DeleteObject",
"Resource": [
"arn:aws:s3:::amplify-xxxxx-amplifys3gen2storagebuck-xxxxx/public/*",
"arn:aws:s3:::amplify-xxxxx-amplifys3gen2storagebuck-xxxxx/protected/${cognito-identity.amazonaws.com:sub}/*",
"arn:aws:s3:::amplify-xxxxx-amplifys3gen2storagebuck-xxxxx/private/${cognito-identity.amazonaws.com:sub}/*"
],
"Effect": "Allow"
}
]
}
ポリシーを見ると、先ほど定義した内容がしっかり反映されていることがわかります。
s3:GetObjectでは、publicとprotectedは全ユーザーが読み取り可能ですが、privateは${cognito-identity.amazonaws.com:sub}が含まれるパスのみ許可されています。これにより、他のユーザーのprivateフォルダにはアクセスできない仕組みになっています。
s3:PutObjectとs3:DeleteObjectでは、publicは全認証ユーザーが操作可能ですが、protectedとprivateは自分のユーザーのIDに紐づくパスのみ許可されています。
数行の宣言的な定義から、適切なCondition句を含む複雑なIAMポリシーが自動生成されるのは便利ですね。手動でこれを書くとなると、設定ミスや考慮漏れが起きやすいポイントなので、ありがたい機能だと思います。
サンプルアプリケーションについて
今回作成したサンプルアプリケーションでは、Cognitoを使ったユーザー認証、public/protected/privateフォルダへのファイルアップロード、そしてファイル一覧表示とダウンロード機能を実装しています。
もし挙動が気になるようであればCloneしてぜひお試しください。
実際に使ってみた感想
裏側では適切なIAMポリシーが自動生成されるため、S3バケットポリシーやCognito Identity Poolの設定を手動で書く必要がありません。細かいIAMポリシーを意識せずに、直感的にアクセス制御を実装できるのは嬉しいです。
さらに、動的な権限判定(例:組織横断のアクセス制御)が必要な場合は、LambdaをプロキシしてS3のファイルを処理するアプローチも検討する価値があるかもしれません。テナントIDなどのカスタム属性の活用や、より複雑なビジネスロジックに基づく権限制御が必要な場合は、別のアーキテクチャを検討すると良いと思います。シンプルに個人の権限レベルで分離したい場合は今回の機能活用がいいかもしれませんね。
複雑な設定をするケースは下記みたいなアーキテクチャイメージです。

おわりに
今回は、Amplify Gen2を使ったS3フォルダ単位のアクセス制御を確認してみました。
従来はIAMポリシーやS3バケットポリシーを細かく設定する必要がありましたが、簡単にユーザーベース・グループベースのアクセス制御が実装できました!
特にユーザーごとに個人フォルダを作りたい場合、公開・限定公開・非公開といったアクセスレベルを簡単に制御したい場合に良い機能だなと思いました。
一方で、テナント分離や動的なグループマッピングなどより複雑な動的権限制御が必要な場合は、、LambdaをプロキシしてS3のファイルを処理するアプローチも検討すると良いと思います。
本記事が少しでも参考になりましたら幸いです。最後までご覧いただきありがとうございました!!







