How to upload images to Amazon S3 using the AWS Amplify Storage module
Tags: aws, s3, amplify, react
Published: 2020-04-28 05:42:14

[AWS Amplify](https://aws.amazon.com/amplify/) is a development platform for building secure, scalable mobile and web applications. It provides several [libraries](https://docs.amplify.aws/lib/q/platform/js) for the communication with the AWS services.

In this blog post I show you how to store images (also audio, video, etc. possible) on [Amazon S3](https://aws.amazon.com/s3/) using a [React](https://reactjs.org/) application.

This example uses the [`@aws-amplify/storage`](https://www.npmjs.com/package/@aws-amplify/storage) and the [`@aws-amplify/auth`](https://www.npmjs.com/package/@aws-amplify/auth) package. More on this later.

To manage the infrastructure I use the [Serverless Framework](https://serverless.com/).

## Amazon S3 and Cognito Identity Pool

For the upload we need a S3 bucket to store the files and a Cognito Identity Pool for access control.

### Configure S3 bucket

First of all you need a S3 bucket. I create it as a private bucket called `example-bucket`.

The CORS configuration is important, otherwise some CORS exceptions occur and the upload will not work. You can also define the allowed methods - in the example `GET` and `PUT` are allowed.

```yaml
S3ImageBucket:
  Type: AWS::S3::Bucket
  Properties:
    BucketName: example-bucket
    AccessControl: Private
    CorsConfiguration:
      CorsRules:
        -
          AllowedOrigins:
            - '*'
          AllowedHeaders:
            - '*'
          AllowedMethods:
            - GET
            - PUT
          MaxAge: 3000
          ExposedHeaders:
            - x-amz-server-side-encryption
            - x-amz-request-id
            - x-amz-id-2
            - ETag
```

### Configure Cognito Identity Pool

After the S3 bucket has been created, a Cognito Identity Pool must be created.

I use an existing Cognito User Pool as provider. This can be configured with the `CognitoIdentityProviders` option. Of course you can also use another provider. In the policy, I specify which actions may be carried out. In this case `s3:GetObject` and `s3:PutObject`.

```yaml
CognitoIdentityPool:
  Type: AWS::Cognito::IdentityPool
  Properties:
    IdentityPoolName: ${self:service}-${self:provider.stage}-${self:provider.region}-IdentityPool
    AllowUnauthenticatedIdentities: false
    CognitoIdentityProviders:
      - ClientId: 111xxx111xxx111xxx111
        ProviderName: cognito-idp.eu-central-1.amazonaws.com/eu-central-1_XXX

CognitoIdentityPoolRoles:
  Type: AWS::Cognito::IdentityPoolRoleAttachment
  Properties:
    IdentityPoolId:
      Ref: CognitoIdentityPool
    Roles:
      authenticated:
        !GetAtt CognitoAuthRole.Arn

CognitoAuthRole:
  Type: AWS::IAM::Role
  Properties:
    Path: /
    AssumeRolePolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: 'Allow'
          Principal:
            Federated: 'cognito-identity.amazonaws.com'
          Action:
            - 'sts:AssumeRoleWithWebIdentity'
          Condition:
            StringEquals:
              'cognito-identity.amazonaws.com:aud':
                Ref: CognitoIdentityPool
            'ForAnyValue:StringLike':
              'cognito-identity.amazonaws.com:amr': authenticated
    Policies:
      - PolicyName: ${self:service}-${self:provider.stage}-${self:provider.region}-S3CognitoAuthPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: 'Allow'
              Action:
                - 's3:GetObject'
                - 's3:PutObject'
              Resource:
                - !Join [ '', [ !GetAtt S3ImageBucket.Arn, '/*' ]
```

💡 You can also set a role for unauthenticated users via `unauthenticated` if your application requires access to the S3 bucket.

## The Storage module

The `@aws-amplify/storage` module provides a simple mechanism for managing user content for your app in public, protected or private storage buckets.

### Configure Amplify Storage

The configuration is very simple. You only have to set the `bucket` name and the `region` of this S3 bucket.

```js
import Storage from '@aws-amplify/storage'

Storage.configure({
  AWSS3: {
    bucket: 'example-bucket',
    region: 'eu-central-1'
  }
})
```

## The Auth module

Additionally we need the `@aws-amplify/auth` module so that the application can authenticate itself.

### Configure Amplify Auth

The configuration object expects the following parameters:

* `region`: Region of your Amazon Cognito
* `identityPoolId`: ID of your Amazon Cognito Identity Pool
* `userPoolId`: ID of your Amazon Cognito User Pool
* `userPoolWebClientId`: Web Client ID of your Amazon Cognito User Pool

As code it looks like this:

```js
import Auth from '@aws-amplify/auth'

Auth.configure({
  region: 'eu-central-1',
  identityPoolId: 'eu-central-1:xxx-xxx-xxx-xxx-xxxxxx',
  userPoolId: 'eu-central-1_XXX',
  userPoolWebClientId: '111xxx111xxx111xxx111'
})
```

## Using Amplify Storage

Enough configurations, time for usage. 🎉

With the `Storage.put()` function you can put the data to S3. It returns a `{key: S3 Object key}` object on success.

```js
const S3ImageUpload = () => {
  const onChange = async (file) => {
    const { key } = await Storage.put('example.png', file, {
      contentType: 'image/png'
    })

    console.log('S3 Object key', key)
  }

  return (
    <input
      type='file'
      accept='image/png'
      onChange={(e) => onChange(e.target.files[0])}
    />
  )
}
```

With the return value (`key`) and the function `Storage.get()` you can retrieve the image again.

📖 All Storage functions can be found in the [documentation](https://docs.amplify.aws/lib/storage/upload/q/platform/js).