Allowing users to upload content to s3 Allowing users to upload content to s3 express express

Allowing users to upload content to s3


I think what might be better for this case in POSTing directly to S3, skipping your backend server.

What you can do is define a policy that explicitly specifies what can be uploaded to and to where, this policy is then signed using an AWS secret access key (using the AWS sig v4, can generate a policy using this).

An example usage of the policy and signature if viewable in the AWS docs

For your uses you can specify conditions like:

conditions: [   ['content-length-range, 0, '3000000'],   ['starts-with', '$Content-Type', 'image/']]

This will limit uploads to 3Mb, and Content-Type to only items that begin with image/

Additionally, you only have to generate your signature for policy once (or whenever it changes), which means you don't need a request to your server to get a valid policy, you just hardcode it in your JS. When/if you need to update just regenerate the policy and signature and then update the JS file.

edit: There isn't a method through the SDK to do this as it's meant as way of directly POSTing from a form on a webpage, i.e. can work with no javascript.

edit 2: Full example of how to sign a policy using standard NodeJS packages:

import crypto from 'crypto';const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;const ISO_DATE = '20190728T000000Z';const DATE = '20161201';const REGION = process.env.AWS_DEFAULT_REGION || 'eu-west-1';const SERVICE = 's3';const BUCKET = 'your_bucket';if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) {    throw new Error('AWS credentials are incorrect');}const hmac = (key, string, encoding) => {    return crypto.createHmac("sha256", key).update(string, "utf8").digest(encoding);};const policy = {    expiration: '2022-01-01T00:00:00Z',    conditions: [        {            bucket: BUCKET,        },        ['starts-with', '$key', 'logs'],        ['content-length-range', '0', '10485760'],        {            'x-amz-date': ISO_DATE,        },        {            'x-amz-algorithm': 'AWS4-HMAC-SHA256'        },        {            'x-amz-credential': `${AWS_ACCESS_KEY_ID}/${DATE}/${REGION}/${SERVICE}/aws4_request`        },        {            'acl': 'private'        }    ]};function aws4_sign(secret, date, region, service, string_to_sign) {    const date_key = hmac("AWS4" + secret, date);    const region_key = hmac(date_key, region);    const service_key = hmac(region_key, service);    const signing_key = hmac(service_key, "aws4_request");    const signature = hmac(signing_key, string_to_sign, "hex");    return signature;}const b64 = new Buffer(JSON.stringify(policy)).toString('base64').toString();console.log(`b64 policy: \n${b64}`);const signature = aws4_sign(AWS_SECRET_ACCESS_KEY, DATE, REGION, SERVICE, b64);console.log(`signature: \n${signature}\n`);


You need to get familiar with Amazon Cognito and especially with identity pool.

Using Amazon Cognito Sync, you can retrieve the data across client platforms, devices, and operating systems, so that if a user starts using your app on a phone and later switches to a tablet, the persisted app information is still available for that user.

Read more here: Cognito identity pools

Once you create new identify pool, you can reference it while using S3 JavaScript SDK which will allow you to upload content whit out exposing any credentials to the client.

Example here: Uploading to S3

Please read through all of it, especially the section "Configuring the SDK".

The second part of your puzzle - validations.

I would go about implementing a client-side validation (if possible) to avoid network latency before giving an error. If you would choose to implement validation on S3 or AWS Lambda you are looking for a wait-time until file reaches AWS - network latency.


This is something I know we have in our project, so I'll show you part of the codes:

you first need to post to your own server to get the creds for the upload,from that you will return the params from the client upload to S3.

these are params you send to the aws s3 service, you will need the bucket, upload path, and the file

 let params = {        Bucket: s3_bucket,        Key: upload_path,        Body: file_itself    };

this is the code I have for the actual upload to s3

config.credentials = new AWS.Credentials(credentials.accessKeyId, credentials.secretAccessKey, credentials.sessionToken);    let s3 = new S3(config);    return s3.upload(params, options).on("httpUploadProgress", handleProgress);

all of those credentials items you get from your backend of course.