Skip to content

Instantly share code, notes, and snippets.

@projected1
Last active May 16, 2021 11:20
Show Gist options
  • Save projected1/8cb5ad5f5adbd67818745de7075ec53e to your computer and use it in GitHub Desktop.
Save projected1/8cb5ad5f5adbd67818745de7075ec53e to your computer and use it in GitHub Desktop.
Uploading Files to Amazon S3 using Node.js & AWS SDK.

Uploading Files to Amazon S3

There is a specific way to upload files to Amazon S3. Unlike the traditional client-server approach, the client does not need to send any actual files to the server. Instead, the client asks the server for a pre-signed URL to an Amazon S3 bucket and then uploads the files directly to S3. If the client wants to upload multiple files, it needs to get a separate pre-signed URL for each and every file.

Installing the AWS CLI

Visit the AWS CLI web page and follow the installation instructions for our operating system.

The AWS CLI is currently available on:

Configuring the AWS CLI

In order to connect to our AWS account via AWS CLI, we need to make our AWS access keys available:

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY

We can configure them as an environment variables. Alternatively, we can create a credentials configuration file under our home directory:

~
└─.aws
  └─credentials

The credentials file contents:

[default]
AWS_ACCESS_KEY_ID=<your_access_key>
AWS_SECRET_ACCESS_KEY=<your_secret_access_key>

Creating an S3 Bucket

From shell, run the following command:

$ aws S3 mb s3://MY_BUCKET

Note that bucket names are globally shared on AWS so we need to choose a unique name for our bucket.

Setting Up IAM Roles and Permissions

In order to generate upload URLs for S3 bucket, the server needs to assume a role with s3:PutObject permissions. If the server needs to read the uploaded files from S3 bucket, s3:GetObject permissions are also required.

Create an IAM Role & Define the AWS Services that can Assume the Role

Create a role trust policy JSON file (role-policy.json) with the following contents:

{
  "Version": "2012-10-17",
  "Statement": [{
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Create a new role:

$ aws iam create-role --role-name ec2-s3-uploader --assume-role-policy-document file://role-policy.json

Attach the Role to an Existing EC2 Instance

You can associate only one IAM instance profile with an instance.

List the EC2 instances and their states:

$ aws ec2 describe-instances --query "Reservations[*].Instances[*].[InstanceId,KeyName,State.Name,Tags[0].Value,PublicIpAddress]" --output table

Associates an IAM instance profile with a running EC2 instance by specifying the IAM instance profile ARN or the IAM instance profile name.

$ aws ec2 associate-iam-instance-profile --instance-id i-052baf6ba198a8e8a --iam-instance-profile Name="ec2-s3-uploader"

Define the API Actions and Resources the Service Can Use

Create an access policy JSON file (s3-read-write-policy.json) with the following contents:

{
  "Version": "2012-10-17",
  "Statement": [{
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::MY_BUCKET/*"
      ]
    }
  ]
}

Add the access policy to the role:

$ aws iam put-role-policy --role-name ec2-s3-uploader --policy-name s3-read-write-policy-json --policy-document file://s3-read-write-policy.json

Troubleshooting Role Policy Issues

List all role policies:

$ aws iam list-role-policies --role-name  ec2-s3-uploader

You should get a list of all the policies attached to a role:

{
  "PolicyNames": [
    "s3-read-write-policy-json"
  ]
}

Delete a role policy:

$ aws iam delete-role-policy --role-name ec2-s3-uploader --policy-name s3-read-write-policy-json

You can now fix the policy and re-attach it to the role, similarly to how we did it before.

Server Code

The server generates pre-signed URLs to our S3 bucket. These URLs are sent to the client which in turn uses them to upload files directly to the S3 bucket. Uploaded files can be retrieved by the server for internal processing.

Generating pre-Signed S3 Upload URLs

Install the AWS SDK node module:

$ npm i --save aws-sdk

Use the following code to generate a pre-signed upload URL with file size limit of 5 MB and expiration time of 10 minutes.

const aws = require("aws-sdk");
const { v4: uuidv4 } = require("uuid");

/**
 * Generates a pre-signed upload URL with limitation on uploaded file size.
 * This URL should be invoked via HTTP POST.
 *
 * @param  {String} userId   The id of the user who requested the upload URL.
 * @param  {String} filename The original name of the uploaded file.
 * @return {Object}          A pre-signed upload URL.
 */
function getUploadUrl(userId, filename) {
    const params = {
        "Bucket": MY_BUCKET,
        "Fields": {
            "Key": uuidv4(),
            "x-amz-meta-userid": userId,
            "x-amz-meta-filename": filename,
            "success_action_status": "201"
        },
        "Conditions": [
            ["content-length-range", 1024, 5242880] // 1KB - 5MB
        ],
        "Expires": 600
    };

    const s3 = new aws.S3({ "signatureVersion": "v4" });
    return s3.createPresignedPost(params);
}

The response should look similar to this:

{
  "url": "https://s3.amazonaws.com/MY_BUCKET",
  "fields": {
    "Key": "084deb78-8a6a-4c7b-84e9-7cc5c8bcf282",
    "x-amz-meta-filename": "MY_FILE.PNG",
    "success_action_status": "201",
    "bucket": "MY_BUCKET",
    "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
    "X-Amz-Credential": "ASIAV275AUXFJW5OA24C/20210427/us-east-1/s3/aws4_request",
    "X-Amz-Date": "20210427T213411Z",
    "X-Amz-Security-Token": "IQoJb3JpZ2luX2VjENb//////////wEaCXVzLWVhc3QtMSJHMEUCICN2jVmjNG8Fm+5XUQ09DzCnF/qpcwqACJGeYLmjTul5AiEAzNeHn3Q5jgnEpoKBxQ6/CvZCGdbRVOXyaXXop4ZduPIq0QEITxAAGgw0MDE1NzMxOTMxNjIiDITw6H0UYf/HTP6H8yquAfm2o500uvjiwksngr++aTcx3ws/FNQmgMDvy9X4jTWkXtpaS1Hz86hgtF8JarQD0A0y5yMdPjTbCJRlA/04cJC77pKdFfRVDFQ482PXhSNDzmq9cPL14VaFdkyytgSN5KPeIhZxSFZjrxp+Znkwv7LdQ2ncDrq8kGhwjt18y9wB16nkvDja5OOH53l/SoTWy7kh/w7cTQLmth7voRjx9Z371JYb4xbjrHqnNcr5EjDShqKEBjrgAe8txXUh1yfKcqqfszdvuWwrwLVbO0WzaodJu+ju3sg1sCG1lbYEHfOWR3WtdcippARySAwe3kM2mzdkyzg1CvFgFz2R1hub0Uw28f7ykjh1Hj4d0yU6e9KfGoxpwqWAPQgYdPH7OR6omlqqFfxa7wZ8ugVWXV9SSE1dh8daJP7az/nB2qxLa9DAX73rvI5sLmUqwnnZB5Htse85NGhCMCVI06fpe7Jpy3k8p2m8SUJmLcyhFgTwhbDViHrGdvFaEiU/ssAdCByhtbLCeLiW0BPAioUDTsZaiHDwoj3HZudj",
    "Policy": "eyJleHBpcmF0aW9uIjoiMjAyMS0wNC0yN1QyMTozNToxMVoiLCJjb25kaXRpb25zIjpbWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsMSw1MjQyODgwXSx7IktleSI6IjA4NGRlYjc4LThhNmEtNGM3Yi04NGU5LTdjYzVjOGJjZjI4MiJ9LHsieC1hbXotbWV0YS1maWxlbmFtZSI6Im15X2ZpbGUucG5nIn0seyJzdWNjZXNzX2FjdGlvbl9zdGF0dXMiOiIyMDEifSx7ImJ1Y2tldCI6IjBkMGEwNjBiMDUwNiJ9LHsiWC1BbXotQWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsiWC1BbXotQ3JlZGVudGlhbCI6IkFTSUFWMjc1QVVYRkpXNU9BMjRDLzIwMjEwNDI3L3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IlgtQW16LURhdGUiOiIyMDIxMDQyN1QyMTM0MTFaIn0seyJYLUFtei1TZWN1cml0eS1Ub2tlbiI6IklRb0piM0pwWjJsdVgyVmpFTmIvLy8vLy8vLy8vd0VhQ1hWekxXVmhjM1F0TVNKSE1FVUNJQ04yalZtak5HOEZtKzVYVVEwOUR6Q25GL3FwY3dxQUNKR2VZTG1qVHVsNUFpRUF6TmVIbjNRNWpnbkVwb0tCeFE2L0N2WkNHZGJSVk9YeWFYWG9wNFpkdVBJcTBRRUlUeEFBR2d3ME1ERTFOek14T1RNeE5qSWlESVR3NkgwVVlmL0hUUDZIOHlxdUFmbTJvNTAwdXZqaXdrc25ncisrYVRjeDN3cy9GTlFtZ01Ednk5WDRqVFdrWHRwYVMxSHo4NmhndEY4SmFyUUQwQTB5NXlNZFBqVGJDSlJsQS8wNGNKQzc3cEtkRmZSVkRGUTQ4MlBYaFNORHptcTljUEwxNFZhRmRreXl0Z1NONUtQZUloWnhTRlpqcnhwK1pua3d2N0xkUTJuY0RycThrR2h3anQxOHk5d0IxNm5rdkRqYTVPT0g1M2wvU29UV3k3a2gvdzdjVFFMbXRoN3ZvUmp4OVozNzFKWWI0eGJqckhxbk5jcjVFakRTaHFLRUJqcmdBZTh0eFhVaDF5ZktjcXFmc3pkdnVXd3J3TFZiTzBXemFvZEp1K2p1M3NnMXNDRzFsYllFSGZPV1IzV3RkY2lwcEFSeVNBd2Uza00ybXpka3l6ZzFDdkZnRnoyUjFodWIwVXcyOGY3eWtqaDFIajRkMHlVNmU5S2ZHb3hwd3FXQVBRZ1lkUEg3T1I2b21scXFGZnhhN3daOHVnVldYVjlTU0UxZGg4ZGFKUDdhei9uQjJxeExhOURBWDczcnZJNXNMbVVxd25uWkI1SHRzZTg1TkdoQ01DVkkwNmZwZTdKcHkzazhwMm04U1VKbUxjeWhGZ1R3aGJEVmlIckdkdkZhRWlVL3NzQWRDQnlodGJMQ2VMaVcwQlBBaW9VRFRzWmFpSER3b2ozSFp1ZGoifV19",
    "X-Amz-Signature": "8643e95e525976bc194ef379ea07662e4f5d03cc20f40b3dcc4741dfc816bb4a"
  }
}

Key is a generated file name that is used to store the file object in our S3 bucket. We should save this name in our database and use it to retrieve the stored file when needed. We can also save the original file name either as metadata for the file object on S3 (as showcased in the example above) or explicitly in our database.

Retrieving Files from S3

The server can retrieve privately stored files from S3 for internal processing.

const aws = require("aws-sdk");

/**
 * Generates a pre-signed upload URL with limitation on uploaded file size.
 *
 * @param  {String} filename The generate name of the file object on S3.
 * @return {Object}          A promise of the file Buffer.
 */
async function getFileFromS3(filename) {
    const Key = filename;
    const Bucket = "MY_BUCKET";
    const s3 = new aws.S3();
    const res = await s3.getObject({ Bucket, Key }).promise();
    return res.Body;
}

A successful S3 getObject response will look similar to this:

{
  "AcceptRanges": "bytes",
  "LastModified": "2021-04-28T10:33:12.000Z",
  "ContentLength": 22860,
  "ETag": "\"da1e6555706e9cd9db76f14c46c3bdb6\"",
  "ContentType": "binary/octet-stream",
  "Metadata": {
    "userId": "my_user_id",
    "filename": "MY_FILE.PNG"
  },
  "Body": {
    "type": "Buffer",
    "data": [...]
  }
}

Generating pre-Signed S3 Download URLs

Use the following code to generate a pre-signed download URL with expiration time of 1 minute.

const aws = require("aws-sdk");
const { v4: uuidv4 } = require("uuid");

/**
 * Generates a pre-signed download URL with time-to-lve limitation.
 * This URL should be invoked via HTTP GET.
 *
 * @param  {String} filename The generate name of the file object on S3.
 * @return {Object}          A pre-signed download URL.
 */
function getDownloadUrl(filename) {
    const params = {
        "Bucket": MY_BUCKET,
        "Key": filename,
        "Expires": 60
    };

    const s3 = new aws.S3({ "signatureVersion": "v4" });
    return s3.getSignedUrl(params);
}

The response should look similar to this:

https://MY_BUCKET.s3.amazonaws.com/084deb78-8a6a-4c7b-84e9-7cc5c8bcf282?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAV275AUXFDSN73WMN%2F20210516%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210516T110045Z&X-Amz-Expires=60&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDCkn9LpuyxmLqtpwQYzire2K94duH4q69m3YUxdVTnRQIhAKSKXZmhQ6Qw7f7blIA8p%2Bp%2FemgMTO5TjavxqlXzbPj0KtEBCCwQABoMNDAxNTczMTkzMTYyIgynADJBa6UR7ElwCCsqrgGNkGGYdcQC%2F8lbMAlOsNrJvI93KtX9y4wgn6XLqUe1lnB6SGtMMK1vg0L0Gwa02nv309zizPwTwHcZLM4iDHa2bIIdyd4vv0wVpkauDgZ47uIpwKMMcfjjMnXEBjH9jgq07N2KPjg6BrCIF%2BzBTH2KUhF5k%2FOjqQZHvTcfrtO30bgafYE3s1Q8whpYzkMzTVcgV8AfK8C1ReJGxfJtMrACZ7PT41EKlip%2Fodp7O04w3PaDhQY63wEx21%2BWly4cHiYfHKJQzAzz4wN7GF6h3CSzrdyDuEIVA0vWisiBblL17lgNHEngYxHwam%2FlIS4q72%2FJNRBLhcbWN4c0Y5g4xkNfDQzBRVMm3kfrSEwZ6WE1GgVQiDEbcan8bQX0FvBgnhqTPsXaVQcoS9sYoLgbsJgmidobAftBthxPZu5jcCOzs1Iy8rzFymE0J2ZFJPEularHtP3eZlPUBnY9%2BNToeWKDe4CDwinRKq5gD3L1WOCjFdMnin7YVh86d6yatJ0t7JLH8RxtnkqqyeWBw%2Bu%2BX0PfmB83gecd&X-Amz-Signature=c6ebc35378ca5488f7bb9c64eb19df903a352b9884cdaddd38061062b826c7ea&X-Amz-SignedHeaders=host

Client Code

The client should call the server to get a pre-signed upload URL. It can then use this URL to upload files to an S3 bucket via HTTP POST.

Get a pre-Signed Upload URL from Server

function getUploadUrl(filename) {
    const url = "/api/upload-url?filename=" + filename;
    return fetch(url)
        .then(res => res.json())
        .catch(err => {
            throw new Error(
                `Failed to get upload url for: ${filename} ${err}`);
        });
}

Upload the File to S3 Bucket

/**
 * Uploads file to S3 via pre-signed URL.
 *
 * @param {String} data The response from "getUploadUrl".
 * @param {File}   file The file object from the HTML input element.
 *                      e.g. <code>document.querySelector('input[type="file"]').files[0]</code>
 */
function uploadFile(data, file) {
    const form = new FormData();
    Object.entries(data.fields).forEach(
        entry => form.append(entry[0], entry[1]));
    form.append("file", file);

    const options = {
        "method": "POST",
        "body": form
    };

    return fetch(data.url, options)
        .then(res => {
            if (res.status !== 201) {
                throw new Error(
                    `HTTP ${res.status} ${res.statusText}`);
            }
            return res;
        })
        .catch(err => {
            throw new Error(
                `Failed to upload file: ${file.name} ${err}`);
        });
}

On successful upload, S3 returns HTTP 201 Created and a response body similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<PostResponse>
  <Location>https://s3.amazonaws.com/MY_BUCKET/05c710a8-fb23-464b-84c7-37b1e40ff573</Location>
  <Bucket>MY_BUCKET</Bucket>
  <Key>05c710a8-fb23-464b-84c7-37b1e40ff573</Key>
  <ETag>"da1e6555706e9cd9db76f14c46c3bdb6"</ETag>
</PostResponse>

Get a pre-Signed Download URL from Server

function getDownloadUrl(filename) {
    const url = "/api/download-url?filename=" + filename;
    return fetch(url)
        .then(res => res.text())
        .catch(err => {
            throw new Error(
                `Failed to get download url for: ${filename} ${err}`);
        });
}

Download the File from S3

function downloadFile(url) {
    window.location.href = url;
}

S3 Errors

You can find a complete list of errors on the Amazon S3 documentation page

If upload URL expires for any reason, the client can request a new upload URL from our server and then try uploading to S3 again.

Expired URL Policy

If the URL policy expires, S3 returns HTTP 403 Forbidden and a response body similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>AccessDenied</Code>
  <Message>Invalid according to Policy: Policy expired.</Message>
  <RequestId>J0B48CGERN0RMT9T</RequestId>
  <HostId>diIQA8/OGci3Vic9h9nnIFMs793LlmW040EffsjL7cC8rbBJMMfVO7SyT7bE4Vw43WqJfo8qws4=</HostId>
</Error>

Expired Security Token

If the security token expires, S3 returns HTTP 400 Bad Request and a response body similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>ExpiredToken</Code>
  <Message>The provided token has expired.</Message>
  <Token-0>IQoJb3JpZ2luX2VjENb//////////wEaCXVzLWVhc3QtMSJHMEUCICN2jVmjNG8Fm+5XUQ09DzCnF/qpcwqACJGeYLmjTul5AiEAzNeHn3Q5jgnEpoKBxQ6/CvZCGdbRVOXyaXXop4ZduPIq0QEITxAAGgw0MDE1NzMxOTMxNjIiDITw6H0UYf/HTP6H8yquAfm2o500uvjiwksngr++aTcx3ws/FNQmgMDvy9X4jTWkXtpaS1Hz86hgtF8JarQD0A0y5yMdPjTbCJRlA/04cJC77pKdFfRVDFQ482PXhSNDzmq9cPL14VaFdkyytgSN5KPeIhZxSFZjrxp+Znkwv7LdQ2ncDrq8kGhwjt18y9wB16nkvDja5OOH53l/SoTWy7kh/w7cTQLmth7voRjx9Z371JYb4xbjrHqnNcr5EjDShqKEBjrgAe8txXUh1yfKcqqfszdvuWwrwLVbO0WzaodJu+ju3sg1sCG1lbYEHfOWR3WtdcippARySAwe3kM2mzdkyzg1CvFgFz2R1hub0Uw28f7ykjh1Hj4d0yU6e9KfGoxpwqWAPQgYdPH7OR6omlqqFfxa7wZ8ugVWXV9SSE1dh8daJP7az/nB2qxLa9DAX73rvI5sLmUqwnnZB5Htse85NGhCMCVI06fpe7Jpy3k8p2m8SUJmLcyhFgTwhbDViHrGdvFaEiU/ssAdCByhtbLCeLiW0BPAioUDTsZaiHDwoj3HZudj</Token-0>
  <RequestId>G9EMMHNNBTVH7WFZ</RequestId>
  <HostId>OhQnjUs2Et7aAjI5BaPSHRH98Ssrzx3fwGXORhL6BPxr9PxzhYCLFTsp0Girq/aebZZf3ZvZGuc=</HostId>
</Error>

Exceeding File Size Limits

If the size of the uploaded file exceeds the maximum allowed size, S3 returns HTTP 400 Bad Request and aborts the connection without any additional information.

Testing

We will use cURL to test our file upload flow.

Get Upload URL

$ curl -v https://MY_SERVER/api/upload-url?filename=MY_FILE.PNG

Upload a File

Transform the response data to cURL request arguments (FormData):

function toFormData(data) {
    return Object.entries(data.fields).map(entry => `-F ${entry[0]}=${entry[1]}`).join('\n');
}

Upload the file to S3 bucket:

$ curl -v -X POST \
  -F Key=05c710a8-fb23-464b-84c7-37b1e40ff573 \
  -F x-amz-meta-filename=MY_FILE.PNG \
  -F success_action_status=201 \
  -F bucket=MY_BUCKET \
  -F X-Amz-Algorithm=AWS4-HMAC-SHA256 \
  -F X-Amz-Credential=ASIAV275AUXFNSN4XC7B/20210428/us-east-1/s3/aws4_request \
  -F X-Amz-Date=20210428T103234Z \
  -F X-Amz-Security-Token=IQoJb3JpZ2luX2VjEOP//////////wEaCXVzLWVhc3QtMSJIMEYCIQDKl+YvyZVYvdUQv8XOvJeq1He6XBOSyc2gxoK9tS4C/AIhAJr2/bkkOaBS2WTDFb5L3yGI0En3RmJJVQi+ppozA19zKtEBCFwQABoMNDAxNTczMTkzMTYyIgwDzNvwtBxeVYWkP5wqrgGTEcnavOvVlQHQad5j8NTbMswKLvgLvA7Ov+amGDN+I85WANuPhsTsOQlBo6NL1UBSQ8acY1CqC0c4iFeJi+dAj1iGgoUeyb5JMOC3tgHF3AbDdsZeVOT1+wPBz81JFnuL4+WglVFTfHi6CJe6nyb3dc1eqnyZ+/xrXjrmi3IXLDIPwBMDdfpNUUP7gI2J2NBvZyr8mnJpJKGYW3f/cmPg6Ugnk5x78fG661Ox5XQwtfKkhAY63wESkvFYZsoiqv5e4sjU+KGocvc/61/6VUq6nMnEvkdctHYmgC1hHiL66IiGqwG2XGo15SBUlJ2b4XFMHMm3fbJGitqP8hEjZvd8OLh7tLhUiL25078lGkLWkF4+IWXvhwghGvSpBbB1fuDNknwJgqN1vxS0kt3WwkXQdTh4O7LafOXI9rWglA+T2ub3RcwTdeI88Q9rmIhtHH4LEEErLMFMReeoTrDjZxzjDve3YOpPtU6yKlpsVwjkmVhbjGN3VR8k9rvu9BoCU8ioVCMyHRfhkfeU8m3pxCF/Tdgyo/2t \
  -F Policy=eyJleHBpcmF0aW9uIjoiMjAyMS0wNC0yOFQxMDozMzozNFoiLCJjb25kaXRpb25zIjpbWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsMSw1MjQyODgwXSx7IktleSI6IjA1YzcxMGE4LWZiMjMtNDY0Yi04NGM3LTM3YjFlNDBmZjU3MyJ9LHsieC1hbXotbWV0YS1maWxlbmFtZSI6Im15X2ZpbGUucG5nIn0seyJzdWNjZXNzX2FjdGlvbl9zdGF0dXMiOiIyMDEifSx7ImJ1Y2tldCI6IjBkMGEwNjBiMDUwNiJ9LHsiWC1BbXotQWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsiWC1BbXotQ3JlZGVudGlhbCI6IkFTSUFWMjc1QVVYRk5TTjRYQzdCLzIwMjEwNDI4L3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IlgtQW16LURhdGUiOiIyMDIxMDQyOFQxMDMyMzRaIn0seyJYLUFtei1TZWN1cml0eS1Ub2tlbiI6IklRb0piM0pwWjJsdVgyVmpFT1AvLy8vLy8vLy8vd0VhQ1hWekxXVmhjM1F0TVNKSU1FWUNJUURLbCtZdnlaVll2ZFVRdjhYT3ZKZXExSGU2WEJPU3ljMmd4b0s5dFM0Qy9BSWhBSnIyL2Jra09hQlMyV1RERmI1TDN5R0kwRW4zUm1KSlZRaStwcG96QTE5ekt0RUJDRndRQUJvTU5EQXhOVGN6TVRrek1UWXlJZ3dEek52d3RCeGVWWVdrUDV3cXJnR1RFY25hdk92VmxRSFFhZDVqOE5UYk1zd0tMdmdMdkE3T3YrYW1HRE4rSTg1V0FOdVBoc1RzT1FsQm82TkwxVUJTUThhY1kxQ3FDMGM0aUZlSmkrZEFqMWlHZ29VZXliNUpNT0MzdGdIRjNBYkRkc1plVk9UMSt3UEJ6ODFKRm51TDQrV2dsVkZUZkhpNkNKZTZueWIzZGMxZXFueVorL3hyWGpybWkzSVhMRElQd0JNRGRmcE5VVVA3Z0kySjJOQnZaeXI4bW5KcEpLR1lXM2YvY21QZzZVZ25rNXg3OGZHNjYxT3g1WFF3dGZLa2hBWTYzd0VTa3ZGWVpzb2lxdjVlNHNqVStLR29jdmMvNjEvNlZVcTZuTW5FdmtkY3RIWW1nQzFoSGlMNjZJaUdxd0cyWEdvMTVTQlVsSjJiNFhGTUhNbTNmYkpHaXRxUDhoRWpadmQ4T0xoN3RMaFVpTDI1MDc4bEdrTFdrRjQrSVdYdmh3Z2hHdlNwQmJCMWZ1RE5rbndKZ3FOMXZ4UzBrdDNXd2tYUWRUaDRPN0xhZk9YSTlyV2dsQStUMnViM1Jjd1RkZUk4OFE5cm1JaHRISDRMRUVFckxNRk1SZWVvVHJEalp4empEdmUzWU9wUHRVNnlLbHBzVndqa21WaGJqR04zVlI4azlydnU5Qm9DVThpb1ZDTXlIUmZoa2ZlVThtM3B4Q0YvVGRneW8vMnQifV19 \
  -F X-Amz-Signature=ef408b1e4c7de3728cda3d7c9a5349567a161ba67459bd6349c49a373881c1c4 \
  -F file=@MY_FILE.PNG \
  https://s3.amazonaws.com/MY_BUCKET

List Files in S3 Bucket

$ aws s3 ls s3://MY_BUCKET

Get Upload URL

$ curl -v https://MY_SERVER/api/download-url?filename=084deb78-8a6a-4c7b-84e9-7cc5c8bcf282
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment