respectful
respectful

Reputation: 137

How to correctly setup signed url uploads for AWS S3 on Heroku?

The Problem

I have a simple application running on Heroku where users can upload and view images/videos. I'm trying to use signed url. I tried this tutorial and this one. In fact I cloned the Github repo. The problem is when ever I try to upload an image I get these errors:

[Error] Preflight response is not successful

[Error] XMLHttpRequest cannot load (long link) due to access control checks.

[Error] Failed to load resource: Preflight response is not successful (img.jp2, line 0)

Specific Question

I think the problem might be with the S3 bucket set up since I do not have much experience with AWS. So my question is what is causing this issue and how do I fix it?

Here is my CORs config

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

here is the code that generates the Presigned url

aws.config.region = 'us-west-1';
BUCKET = process.env.S3_BUCKET;

app.get('/account', (req, res) => res.render('account.html'));

app.get('/sign-s3', (req, res) => {
  const s3 = new aws.S3();
  const fileName = req.query['file-name'];
  const fileType = req.query['file-type'];
  const s3Params = {
    Bucket: S3_BUCKET,
    Key: fileName,
    Expires: 60,
    ContentType: fileType,
    ACL: 'public-read'
  };

  s3.getSignedUrl('putObject', s3Params, (err, data) => {
    if(err){
      console.log(err);
      return res.end();
    }
    const returnData = {
      signedRequest: data,
      url: `https://${S3_BUCKET}.s3.amazonaws.com/${fileName}`
    };
    res.write(JSON.stringify(returnData));
    res.end();
  });
});

here is the code that tries to upload to S3

function uploadFile(file, signedRequest, url){
  const xhr = new XMLHttpRequest();
  xhr.open('PUT', signedRequest);
  xhr.onreadystatechange = () => {
    if(xhr.readyState === 4){
      if(xhr.status === 200){
        alert('success')
      }
      else{
        alert('Could not upload file.');
      }
    }
  };
  xhr.send(file);
}

What has been tried

adding

<AllowedMethod>OPTIONS</AllowedMethod>

results in

Found unsupported HTTP method in CORS config. Unsupported method is OPTIONS

Additional Notes

I have tried chrome and safari. It turns out I had accidentally defaulted to using

 <AllowedHeader>authorization</AllowedHeader>

changing this back to

 <AllowedHeader>*</AllowedHeader>

results in:

[Long link] Failed to load resource: the server responded with a status of 403 (Forbidden)

Upvotes: 0

Views: 555

Answers (2)

Arun Kamalanathan
Arun Kamalanathan

Reputation: 8583

I think i found the problem, you should set the content-type since it is used when the signature is generated as well.

xhr.setRequestHeader("Content-Type", "application/octet-stream");

Upvotes: 1

respectful
respectful

Reputation: 137

The Heroku tutorial left some details out (or needs to be updated):

The config needed to be updated:

aws.config.region = 'us-west-1';
aws.config.update({
  accessKeyId:  process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
})

Upvotes: 0

Related Questions