I'm trying to implement a picture upload functionality for a Vue app using AWS S3 a pre-signed URL. The first step is to send a request to an API that will create the signed URL to upload the file. This part works fine:
Server side:
'use strict'; const aws = require('aws-sdk'); const config = require('../config'); const util = require('./util'); const uuidv4 = require('uuid/v4'); const bucketName = 'myAmazonS3Bucket'; aws.config.update({ secretAccessKey: config.AWS_SECRET_ACCESS_KEY, accessKeyId: config.AWS_ACCESS_KEY_ID, region: 'us-west-2' }); const s3 = new aws.S3({ signatureVersion: 'v4' }); const handler = async (event) => { console.log('Uploading file...'); return await getUploadURL(); } const getUploadURL = async () => { const actionId = uuidv4(); const s3Params = { Bucket: bucketName, Key: `${actionId}.jpg`, ContentType: 'image/jpeg', ACL: 'public-read' }; console.log(s3Params); return new Promise((resolve, reject) => { let uploadURL = s3.getSignedUrl('putObject', s3Params); console.log(uploadURL); resolve({ "statusCode": 200, "isBase64Encoded": false, "headers": { "Access-Control-Allow-Origin": "*" }, "body": JSON.stringify({ "uploadURL": uploadURL, "photoFilename": `${actionId}.jpg` }) }); reject({ "statusCode": 500, "headers": { "Access-Control-Allow-Origin": "*" }, "body": "A funky error occurred and I am not happy about it!" }) }); } module.exports = { handler } The API endpoint sends a response similar to this one:
{ "uploadURL": "https://s3.us-west-2.amazonaws.com/pics.amazon-clone.io/7925d452-cadd-4f06-ba63-cc50645e3cfb.jpg?AWSAccessKeyId=AKIASGDJJ5ZLUVPMUYMQ&Content-Type=image%2Fjpeg&Expires=1580276753&Signature=3rqNckP4DiL6DkWPRuEGJsuIGpw%3D&x-amz-acl=public-read", "photoFilename": "7925d452-cadd-4f06-ba63-cc50645e3cfb.jpg" } The client will use the uploadUrl to upload the file to the S3 bucket. Here is the client code for that:
uploadImage: async function (e) { console.log('Upload clicked') console.log(e) // Get the presigned URL const response = await axios({ method: 'POST', url: API_ENDPOINT }) console.log('Response: ', response.data) console.log('Uploading: ', this.image) let binary = atob(this.image.split(',')[1]) let array = [] for (var i = 0; i < binary.length; i++) { array.push(binary.charCodeAt(i)) } let blobData = new Blob([new Uint8Array(array)], {type: 'image/jpeg'}) console.log('Uploading to: ', response.data.uploadURL) const result = await fetch(response.data.uploadURL, { method: 'PUT', headers: { 'Content-Type': 'image/jpeg', 'x-amz-acl': 'public-read' }, body: blobData }) console.log('Result: ', result) // Final URL for the user doesn't need the query string params this.uploadURL = response.data.uploadURL.split('?')[0] } Unfortunately, I'm getting a forbidden 403 error when using the signed URL. Here is the result of the console errors I get from my Chrome browser:
Access to fetch at 'https://s3.us-west-2.amazonaws.com/pics.amazon-clone.io/b1bdb5e3-7f64-49f7-b779-11b3f67317ee.jpg?Content-Type=image%2Fjpeg&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIASGDJJ5ZLUVPMUYMQ%2F20200129%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20200129T165522Z&X-Amz-Expires=900&X-Amz-Signature=b230c9a40065585307e150655466bbab3d0d99aa43f8620377ab977eb1c7234c&X-Amz-SignedHeaders=host%3Bx-amz-acl&x-amz-acl=public-read' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.