AWS Amplify: How to interact with an existing S3 bucket
Tags: aws, awsamplify, javascript
Published: 2020-10-09 16:23:44

The [AWS Amplify](https://docs.amplify.aws/) framework provides solutions that allows Frontend and Mobile web developers to easily implement solutions that interact with resources in the AWS cloud. For example, the [Amplify CLI](https://docs.amplify.aws/cli) allows you to create a fully configured and secure S3 bucket to store items. Meanwhile, the [Amplify Storage module](https://docs.amplify.aws/lib/storage/getting-started/q/platform/js) lets you easily list the content of your bucket, upload items, and fetch items.

The Amplify libraries are not just great for interacting with resources created by the Amplify CLI. As highlighted [here](https://docs.amplify.aws/lib/storage/getting-started/q/platform/js#manual-setup-import-storage-bucket), you can manually configure your application to interact with an existing S3 bucket. In this article, I’m going to show you how you can set up an Amplify application to interact with an existing S3 bucket.

Let’s take a very common situation where you have an S3 bucket that was created outside an Amplify project, and that is used as an origin for a [Amazon CloudFront](https://aws.amazon.com/cloudfront/) distribution. The origin content is stored under a [prefix](https://docs.aws.amazon.com/general/latest/gr/glos-chap.html#keyprefix) in the bucket. Public access to the bucket is not allowed. Let’s set up an app that lets authenticated users manage files in the bucket.

## Setting up your Amplify app
In your application folder, initialize Amplify and follow the prompt to configure.

```sh
$ amplify init
```

Initializing the app creates two IAM roles: `AuthRole` and `UnauthRole`. These roles are respectively used by authorized users and unauthorized users (if you configure guest access). You’ll use the `AuthRole` role in a bit.

Next set up `auth` to define a Cognito User Pool for your app. This will allow users to sign in.

```sh
$ amp add auth
```

Step through the configuration

```
 Do you want to use the default authentication and security configuration? 
❯ Default configuration

 How do you want users to be able to sign in? (Use arrow keys)
❯ Username
```

Select to configure additional settings. Set up greenlist filtering. This configures a Lambda function that checks the user’s email domain name when they try to register. Only emails with matching domains are permitted to register. You can edit the created Lambda function to implement custom checks and filtering. 

```
 Do you want to configure advanced settings?
❯ Yes, I want to make some additional changes.

 Do you want to enable any of the following capabilities?
❯ Email Domain Filtering (whitelist)

 Do you want to enable any of the following capabilities? Email Domain Filtering (whitelist)
? Enter a comma-delimited list of allowed email domains (example: 'mydomain.com, myotherdomain.com'). your-own-domain.com
```

## Create a policy resource
The next step is to create a policy granting access to the bucket that you will then associate with `AuthRole`. To do this, you will create a new policy category in your Amplify project. I like this approach because it allows to manage the policy independently of the `auth` category. Also your configuration is not lost if you update `auth`.

Open the file `amplify/backend/backend-config.json`, and define a new `s3Auth` resource in the policy category.

```json
"policy": {
    "s3Auth": {
      "service": "iam",
      "providerPlugin": "awscloudformation"
    }
  }
```

Create the following file structure in the `amplify/backend/` directory

```
policy
└── s3Auth
    ├── parameters.json
    └── template.json
```

Edit `policy/s3Auth/parameters.json` to make the `AuthRoleName` value available as a parameter to your template:

```json
{
  "authRoleName": {
    "Ref": "AuthRoleName"
  }
}
```

Edit `policy/s3Auth/template.json` where you define the `s3sappolicy` inline policy for the `AuthRole` (see: `"Roles": [{ "Ref": "authRoleName" }]`). Note the “statement” and the “resource”. You should specify the actions allowed on the bucket and items. You can find some user policy examples [here](https://docs.aws.amazon.com/AmazonS3/latest/dev/example-policies-s3.html). The policy below is an example that limits access to files under `<PREFIX>/`.

```json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "inline policy for s3 access",
  "Parameters": {
    "authRoleName": { "Type": "String" },
    "env": { "Type": "String" }
  },
  "Conditions": {
    "ShouldNotCreateEnvResources": {
      "Fn::Equals": [{ "Ref": "env" }, "NONE"]
    }
  },
  "Resources": {
    "s3sappolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "s3-policy-for-bucket-access",
        "Roles": [{ "Ref": "authRoleName" }],
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "InteractWithObjects",
              "Effect": "Allow",
              "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
              "Resource": "<BUCKET>/<PREFIX>/*"
            },
            {
              "Sid": "ListBucket",
              "Effect": "Allow",
              "Action": "s3:ListBucket",
              "Resource": "<BUCKET>",
              "Condition": {
                "StringLike": {
                  "s3:prefix": ["<PREFIX>", "<PREFIX>/*"]
                }
              }
            }
          ]
        }
      }
    }
  }
}
```

Run `amplify env checkout <current-env-name>` to populate the CLI runtime files and make it aware of the newly added custom resources; then run `amplify push` to deploy your changes.

```shell
$ amplify status

Current Environment: dev

| Category | Resource name              | Operation | Provider plugin   |
| -------- | -------------------------- | --------- | ----------------- |
| Policy   | s3Auth                     | No Change | awscloudformation |
| Function | s5b5b8cbf5b5b8cbfPreSignup | No Change | awscloudformation |
| Auth     | s5b5b8cbf5b5b8cbf          | No Change | awscloudformation |
```

## Configuring your bucket
Your app will initiate cross-origin HTTP requests on your behalf to interact with your S3 bucket. You need to set up a CORS policy on your bucket to allow those requests. 
```xml
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <ExposeHeader>x-amz-server-side-encryption</ExposeHeader>
    <ExposeHeader>x-amz-request-id</ExposeHeader>
    <ExposeHeader>x-amz-id-2</ExposeHeader>
    <ExposeHeader>ETag</ExposeHeader>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
```

You can set this up in the [S3 console](https://s3.console.aws.amazon.com/) by selecting your bucket and opening the **Permissions** tab.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7m303xkburp9ib3kivii.png)


## Connecting your app
At this point, you have created the resources necessary to authenticate users to your app and to access your S3 bucket. Now’s let’s connect your app. Your Amplify project provides an `aws-exports.js` file that has the configuration information for your Cognito User Pool. You need to specify the S3 setting for the Storage module.

```javascript 
import Amplify, { Storage } from 'aws-amplify'
import config from './aws-exports'

Amplify.configure(config)
Amplify.configure({
  Storage: {
    AWSS3: {
      bucket: '<BUCKET>',
      region: '<REGION>',
    },
    customPrefix: {
      private: '<PREFIX>/'
    },
  },
})
Storage.configure({ level: 'private' })
```

Note that there is no concept of public or protected resources for this application. The configuration defines a custom prefix for private level access, and the default access level is also set to private. You can list all files under in the bucket under `<PREFIX>/`

```javascript
async () => {
  const result = await Storage.list('')
  console.log(result)
}
```

All uploads and downloads will be done at the private level as well.

## Conclusion
Whether you are getting started on a brand new project or looking to interact with existing resources on AWS, Amplify provides tools that can help with that. In this article I showed you how to connect to an existing S3 bucket using the Amplify libraries in an Amplify project.  To learn more about how Amplify can help your app dev project, visit the [Amplify Framework Documentation](https://docs.amplify.aws/).