Segment S3 uploads by Cognito user

Photo by Tim Evans on Unsplash.

Continuing my last post about combining AWS Cognito with EvaporateJS to upload to S3, we'll use S3 policies to isolate Cognito users into subdirectories of the S3 bucket.

AWS Policies to Isolate Users

Imagine you want User 1 to have exclusive access to a /Users/1/ subdirectory of your S3 bucket. You could update the S3 bucket's policy to specify that only User 1 has access to /Users/1/. Then what if you want User 2 to have exclusive access to /Users/2/? You could amend the bucket's policy to add rules for User 2. What about users 3, 4, and 5? Or the next 1,000 users of your site? Obviously this approach doesn't scale well.

Luckily AWS supports variables in policies. Instead of explicitly listing users, you could write a generic policy allowing User X to access /Users/X/.

Policy to Isolate Cognito Users

To use this approach with Cognito, I chose the cognito-identity.amazonaws.com:sub policy variable. This evaluates to the Cognito user's Identity ID, which follows the format region:guid. Here's an example policy.

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "AllowAccessToPrivateCognitoUserFolder",
			"Effect": "Allow",
			"Principal": {
				"AWS": ["arn:aws:iam::12345:role/Cognito_Auth_Role"]
			},
			"Action": [
				"s3:GetObject",
				"s3:PutObject",
				"s3:DeleteObject",
				"s3:AbortMultipartUpload"
			],
			"Resource": [
				"arn:aws:s3:::bucket-name/Users/${cognito-identity.amazonaws.com:sub}",
				"arn:aws:s3:::bucket-name/Users/${cognito-identity.amazonaws.com:sub}/*"
			]
		}
	]
}

The highlighted lines require additional attention:

  1. On line 8, enter the role used by your authenticated Cognito users. This is listed in the Cognito area of the AWS Console, under Federated Identities. In the identity pool settings, click "Edit identity pool" in the top-right corner. The role's name is displayed in the "Authenticated role" menu. However, the bucket policy needs the role's full ARN, so you can hop over to the Roles list in IAM, find the role named in the identity pool's settings, and click it to find the Role ARN. Update line 8 with this value, wrapped in double quotes.

  2. If you use Cognito's group feature to assign a custom IAM role to some users, your policy must include the ARN of every role that needs access to S3. Wrap each role ARN in double quotes and separate entries with commas. For example, if your "Moderators" group has a custom IAM role, you would list both roles like:

    "Principal": {
        "AWS": [
            "arn:aws:iam::12345:role/Cognito_Auth_Role",        "arn:aws:iam::12345:role/Cognito_Moderator_Role"    ]
    },
  3. On lines 17 and 18, replace bucket-name with the name of your S3 bucket.

Code to Isolate Cognito Users

Thanks to the AWS policy above, User 2 will be unable to access /Users/1/. But how do we send a user's uploads to the correct subdirectory? Building on the S3Manager code in the previous article, I chose to pass the uploader's ID during instantiation.

const getS3Manager = () => {
	// skipping unchanged code...
	const {
		accessKeyId,
		identityId,		secretAccessKey,
		sessionToken,
	} = AWS.config.credentials;
	const s3Manager = new S3Manager(
		accessKeyId,
		secretAccessKey,
		sessionToken,
		identityId,	);
	// skipping unchanged code...
};
class S3Manager {
	constructor(accessKey, secretKey, sessionToken, userId) {
		// skipping unchanged code...
		this.userId = userId;	}

	upload(file, transferHeaders = {}, options = {}) {
		// skipping unchanged code...
		name: `Users/${this.userId}/encoded/upload/path.extension`, // TODO: Specify this		// skipping unchanged code..
	}
}

Now every Cognito user automatically has a private subdirectory in the S3 bucket that only they can access.

Drew

Drew

Hi! I'm Drew, the Wimpy Programmer. I'm a software developer and formerly a Windows server administrator. I use this blog to share my mistakes and ideas.