Secure S3 Objects Using a Presigned URL

Amazon S3 (Simple Storage Service) is used to store objects, files, or more data that is persistent and easily accessible. We can easily integrate AWS S3 buckets with almost any modern infrastructure such as mobile applications, web applications, etc.

By default, all objects are private — meaning only the bucket account owner initially has access to the object.

Suppose we need to expose an API where the client can upload an image file to S3 and the backend server needs to send back the response with the S3 object URLs. We will go through a secure way of exposing our S3 bucket URLs to the public.

Prerequisites

  1. NodeJS installed on your computer/laptop
  2. AWS SDK for JavaScript
  3. S3 bucket to store objects (files or data)

Note: We need to create an IAM user which has access to both reading and writing objects to S3.

  • Click Next: Permissions
  • Click the Attach existing policies directly box and Create policy
  • Use the visual editor to select the S3 Service. We only need a couple of access requirements, so expand out the access level groups
  • Ensure that GetObject under the READ section and PutObject under the WRITE section are both ticked.
  • Set the resources you want to grant access to, specify the bucket name you created earlier and click any for the object name.
  • Click Review Policy and enter a name for the policy. Save the policy
  • Apply the new policy to the new user you have created and take note of the aws access credentials.

So what are presigned URLs anyway?


A presigned URL is a URL that you can provide to your users to grant temporary access to a specific S3 object. Using the URL, a user can either READ the object or WRITE an Object (or update an existing object). The URL contains specific parameters which are set by your application. Here is how a Presigned URL looks like:

https://bucket.s3.region.amazonaws.com/myfile.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=random-aws-credential-to-identify-the-signer&X-Amz-Date=timestamp-of-generation-of-url&X-Amz-Expires=validity-from-generation-timestamp&X-Amz-Signature=6ffca338-f5b2-48ad-89ec-4ae462cb46dc&X-Amz-SignedHeaders=host

Formatted Presigned URL

https://bucket.s3.region.amazonaws.com/myfile.jpg ?
X-Amz-Algorithm     =   AWS4-HMAC-SHA256 &
X-Amz-Credential    =   random-aws-credential-to-identify-the-signature &
X-Amz-Date          =   timestamp-of-generation-of-url &
X-Amz-Expires       =   validity-from-generation-timestamp &
X-Amz-Signature     =   6ffca338f5b248ad89ec4ae462cb46dc &
X-Amz-SignedHeaders =   host

A pre-signed URL uses three parameters to limit the access to the user:

  • Bucket: The bucket that the object is in (or will be in)
  • Key: The name of the object
  • Expires: The amount of time that the URL is valid

Generate Presigned URL for S3 Bucket using JavaScript

The code can be found on s3-presigned-urls repo. It contains two helper classes to generate presigned URLs. Simply run npm run get or npm run put to get the GET or PUT URLs respectively, they will be logged on the console.

  1. To generate a GET URL for an object stored in the S3 bucket, we can use the code below:
require('dotenv').config();

var AWS = require('aws-sdk');

var credentials = {
    accessKeyId: process.env.S3_ACCESS_KEY,
    secretAccessKey : process.env.S3_SECRET_KEY
};

var region = process.env.S3_REGION
var bucket = process.env.S3_BUCKET

AWS.config.update({credentials, region});

var s3 = new AWS.S3();

var presignedGETURL = s3.getSignedUrl('getObject', {
    Bucket: bucket,
    Key: 'logo.png', 
    Expires: 100 
});

console.log(presignedGETURL);

Simply open the browser and paste in the generated URL to view your S3 object

2. To generate a PUT URL for an object we wish to upload to the S3 bucket, we can use the code below:

require('dotenv').config();

var AWS = require('aws-sdk');

var credentials = {
    accessKeyId: process.env.S3_ACCESS_KEY,
    secretAccessKey : process.env.S3_SECRET_KEY
};

var region = process.env.S3_REGION
var bucket = process.env.S3_BUCKET

AWS.config.update({credentials, region, signatureVersion: 'v4'});

var s3 = new AWS.S3();

var presignedPUTURL = s3.getSignedUrl('putObject', {
    Bucket: bucket,
    Key: 'spacex.png', 
    Expires: 100 
});

console.log(presignedPUTURL);

This will generate a URL a secure URL we can use to upload an object, we can use POSTMAN in the configuration as per below to upload. You can attach a file in the body of the PUT request in a binary format:

With a correct configuration, the file should be uploaded to your chosen S3 bucket:

Troubleshooting

Remember you need to add a .env file containing the environment variables below and specify your values:

S3_ACCESS_KEY=anaccesskeyishere
S3_SECRET_KEY=asecretkeyishere
S3_BUCKET=presignedurldemo
S3_REGION=us-east-1

Feel free to clone the s3-presigned-urls repo from GitHub if you are having issues following along.

Summary

We have successfully created the Presigned URL for our S3 bucket and now we can securely share this URL with our client. The client will be able to access the resources up to the expiry time of the Presigned URL. Also, the client is able to upload the files to S3 using the Presigned URL up to expiry time.