How to Upload Files to S3 from the Browser – Angular

How to Upload Files to S3 from the Browser – Angular

In this tutorial, I will guide you step by step on how to upload files to S3 from the browser using Angular and also how you can add security to it using AWS’s pre-signed URL.

To directly Upload files to S3 from the browser using Angular, you should do the following 7 steps:

  1. Create IAM user in AWS
  2. Create an S3 Bucket with Bucket Policy & CORS
  3. Create Angular Application
  4. Install the AWS-SDK package.
  5. Add AWS credentials in your environment file.
  6. Upload File Logic to S3 with Angular Service
  7. Add security in the upload process using a pre-signed URL.

1. Create an IAM user in AWS

The first step is to create an IAM user in AWS Management Console, use the following link to create an IAM user. Create IAM using AWS Management Console

During the creation process, you will be asked to assign the user to a group, Create a group named admin if you don’t have one, and add the Administrator policy.

Once you are done with the user creation, you will be provided with the Access Key ID & Secret Access key to access the AWS services.

2. Create an S3 Bucket with Bucket Policy & CORS

To create an S3 bucket, go to AWS Management Console. Navigate to S3 under the services menu, there you can create a bucket using the create bucket option.

For detailed information on how to create an S3 bucket please follow the following article: How to Create an S3 bucket in AWS Step By Step?

Add Bucket Policy in AWS

To upload files to your bucket, you should add a bucket policy. A bucket policy is a resource-based policy that you can use to grant access permissions to your bucket and the objects in it. 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<your_user_id>:user/<your_username>"
            },
            "Action": [
                "s3:ListBucket",
                "s3:ListBucketVersions",
                "s3:GetBucketLocation",
                "s3:Get*",
                "s3:Put*"
            ],
            "Resource": "arn:aws:s3:::<your_bucket_name>"
        }
    ]
}

Inside the bucket detail page, navigate to the “permissions” tab and there you can add the above bucket policy by clicking the edit button inside the bucket policy section.

The above policy contains, the IAM user who can access the files and also the bucket ARN. You can get your user ARN and bucket ARN from the user details and bucket details page.

Add CORS policy

To access your bucket from the front-end application, you should add the CORS origin policy. Copy paste the following CORS policy into your CORS section of the bucket details page.

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    }
]

The above CORS policy enables for all origins. If you want to specify a particular domain add the domain inside “AllowedOrigins”:

 "AllowedOrigins": [
     "example.com"
  ],

3. Create an Angular Application

Create an angular application using the following command:

ng new aws-s3-bucket

4. Install @aws-sdk/client-s3 Dependency

Install @aws-sdk/client-s3 dependency using the following command. This is an official package from AWS, it’s an SDK for S3.

npm install --save @aws-sdk/client-s3

In your polyfills.ts, add the following code

(window as any).global = window;

5. Add AWS Credentials in your environment.ts file.

In order to access our created bucket, You need to have our access key & secret key. Copy your access key id & secret access key from your IAM console.

Keep it in your environtment.ts files, like AWS_ACCESS_KEY_ID: <your_access_key_id> and SECRET_ACCESS_KEY: <your_secret_access_key>.

export const environment = {
  production: false,
  AWS_ACCESS_KEY_ID: "<your_aws_access_key_id>",
  AWS_SECRET_ACCESS_KEY: "<your_aws_secret_access_key>",
  AWS_REGION: "<your_aws_region>"
};

6. Upload File to S3 Logic with Angular Service

To keep the S3-related upload logic in a singleton file, Create an angular service using the following command:

ng g service s3-service

Import the dependency in your s3-service at the top of your file.

// at the top of your s3-service.service.ts
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

Create an Instance of an S3 bucket in your Service

To create an S3 bucket instance in your angular application, you need to pass the access key id, secret access key, and the region of your bucket.

export class S3ServiceService {
  private bucket: S3Client;

  constructor(private http: HttpClient) {
    this.bucket = new S3Client(
      {
        credentials: {
          accessKeyId: environment.AWS_ACCESS_KEY_ID,
          secretAccessKey: environment.AWS_SECRET_ACCESS_KEY,
        },
        region: environment.AWS_REGION,
      }
    );
   }
}

Inside the constructor, we are calling S3Client class using the credentials and region and keep that object inside the variable bucket to keep a single instance of it.

Upload Files to S3 using the S3Client.send() method

In your service, create a method called uploadFile(file: File). In this method, We can use this.bucket.send(params) to upload the file which will return a promise as a response.

  async uploadFile(file: File) {

    const params = {
      Bucket: 'your_bucket_name',
      Key: file.name,
      Body: file,
      ACL: 'public-read',
      ContentType: file.type
    };

    try {
      const response = await this.bucket.send(new PutObjectCommand(params));
      console.log("SUCCESS", response);
    } catch(error) {
      console.log("FAILURE", error);
    }
   
  }

The above uploadFile method is prefixed with async since our this.bucket.send is returning a promise. Wrap that with a try/catch block to handle success and error responses.

You should pass the PutObjectCommand object as a parameter to the uploadFile method. The parameters for the PutObjectCommand should contain Bucket (your bucket name), Key (filename or path with filename), Body (file), ACL, and ContentType (file type).

Add the File input field in your app.component.html

In your app.component.html, add the input type file HTML element, onFileSelect of the element call uploadFile method of your app.component.html

<label>
  <input type="file" name="fileUpload" (change)="onFileSelect($event)" />
</label>

Add onFileSelect method & S3 Service in your app.component.ts

In your app.component.ts, inject s3-service in your constructor, and then create a method called onFileSelect which accepts event as a parameter.

Inside the onFileSelect method, trigger the s3serivce.uploadFile() method with selected file as a parameter

import { Component } from '@angular/core';
import { S3ServiceService } from './s3-service.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  constructor(private s3Service: S3ServiceService) { }


  onFileSelect(e: any) {
    this.s3Service.uploadFile(e.target.files[0]);
  }
}

7. Securely Upload Files to S3 Bucket using a pre-signed URL

To do the upload process with security, you should use the pre-signed URL in AWS. It’s a good practice to generate a pre-signed URL from your backend service with an expiration time, but for brevity, I am going to generate it in the front-end. Using that URL in the frontend you can trigger the HTTP put method to upload your file.

What is a pre-signed URL in S3 AWS?

A pre-signed URL gives you access to the object/files identified in the URL, provided that the creator of the pre-signed URL has permission to access that object.

How to generate a pre-signed URL in S3 AWS?

To generate a pre-signed URL, You should install the following package and import getSignedUrl method in your service file from the package.

npm install --save @aws-sdk/s3-request-presigner
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

Inside your s3-service, add another method uploadFileWithPreSignedURL which accepts a file as parameter.

  async uploadFileWithPreSignedURL(file: File) {
    const contentType = file.type;
  
    const params = {
      Bucket: 'your_bucket_name',
      Key: file.name,
      ACL: 'public-read',
      ContentType: contentType
    };

    const command = new PutObjectCommand(params)

    try {
      const preSignedURL = await getSignedUrl(this.bucket, command, { expiresIn: 3600});
      
      this.http.put(preSignedURL, file).subscribe({
        next: (res) => {
          console.log("SUCCESS", res);
        },
        error: (err) => {
          console.log("FAILED", err);
        },
        complete: () => {
          console.log("DONE")
        }
      })
    } catch(err) {
      console.log(err);
    }
  }

In the above method, we are triggering getSignedUrl method to generate pre-signed URL. It accepts bucket (S3Client instance, we created inside the constructor of our service file), command (in our case, its PutObjectCommand), and the expiration time in the config object as the last parameter.

Using the generated pre-signed URL, we are making an HTTP put request with FILE as the body of the request. Make sure to import HTTPClientModule in your app.module.ts and import the HttpClient in your service constructor.

Conclusion:

In this detailed tutorial, you have learned how to upload files directly to S3 using Angular with both access key & secret access id, and also you learned how to securely do that using a pre-signed URL in AWS. If you are stuck on something or need any help in this process, feel free to ask your questions in the comment section below.

Full Code is Available on Github: https://github.com/bearnithi/aws-s3-bucket-angular