Home > Software engineering >  Upload a file to Amazon S3 with presigned url PUT request but get error "SignatureDoesNotMatc
Upload a file to Amazon S3 with presigned url PUT request but get error "SignatureDoesNotMatc

Time:09-17

I am trying to upload files to Amazon S3 with a presigned url with javascript fetch() as following(copy past from the chrome Developer tool)

fetch("https://.....amazonaws.com/.../copy.mp3?AWSAccessKeyId=...&Signature=...&Expires=1631443785", {
  "headers": {
    "content-type": "audio/mpeg",
    "sec-ch-ua": "\"Google Chrome\";v=\"93\", \" Not;A Brand\";v=\"99\", \"Chromium\";v=\"93\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"macOS\""
  },
  "referrer": "http://localhost:3000/",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "method": "PUT",
  "mode": "cors",
  "credentials": "omit"
});

and it returns the following error "SignatureDoesNotMatch":

<Error>
 <Code>SignatureDoesNotMatch</Code>
 <Message>The request signature we calculated does not match the signature you provided. 
 Check your key and signing method.</Message>
 <AWSAccessKeyId>...</AWSAccessKeyId>
 <StringToSign>GET 1631443785 ..../copy.mp3</StringToSign>
...
</Error>

The StringToSign in the error says it's a "GET" request while I think that I am sending a PUT with fetch().

Besides, I try to run $curl like:

$curl --request PUT --upload-file copy.mp3  https://....'

and it works.

Anything I am doing wrong here?

Thank you in advance.. I have been blocked here for 2 days :(

CodePudding user response:

There is an example on how to do this using fetch in the aws docs:


          // Import the required AWS SDK clients and commands for Node.js
import {
  CreateBucketCommand,
  DeleteObjectCommand,
  PutObjectCommand,
  DeleteBucketCommand }
from "@aws-sdk/client-s3";
import { s3Client } from "./libs/s3Client.js"; // Helper function that creates Amazon S3 service client module.
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import fetch from "node-fetch";

// Set parameters
// Create a random names for the Amazon Simple Storage Service (Amazon S3) bucket and key
export const bucketParams = {
  Bucket: `test-bucket-${Math.ceil(Math.random() * 10 ** 10)}`,
  Key: `test-object-${Math.ceil(Math.random() * 10 ** 10)}`,
  Body: "BODY"
};
export const run = async () => {
  try {
    // Create an Amazon S3 bucket.
    console.log(`Creating bucket ${bucketParams.Bucket}`);
    await s3Client.send(new CreateBucketCommand({ Bucket: bucketParams.Bucket }));
    console.log(`Waiting for "${bucketParams.Bucket}" bucket creation...`);
  } catch (err) {
    console.log("Error creating bucket", err);
  }
  try {
    // Create the command.
    const command = new PutObjectCommand(bucketParams);

    // Create the presigned URL.
    const signedUrl = await getSignedUrl(s3Client, command, {
      expiresIn: 3600,
    });
    console.log(
      `\nPutting "${bucketParams.Key}" using signedUrl with body "${bucketParams.Body}" in v3`
    );
    console.log(signedUrl);
    const response = await fetch(signedUrl);
    console.log(
      `\nResponse returned by signed URL: ${await response.text()}\n`
    );
    return response;
  } catch (err) {
    console.log("Error creating presigned URL", err);
  }
  try {
    // Delete the object.
    console.log(`\nDeleting object "${bucketParams.Key}"} from bucket`);
    await s3Client.send(
      new DeleteObjectCommand({ Bucket: bucketParams.Bucket, Key: bucketParams.Key })
    );
  } catch (err) {
    console.log("Error deleting object", err);
  }
  try {
    // Delete the Amazon S3 bucket.
    console.log(`\nDeleting bucket ${bucketParams.Bucket}`);
    await s3.send(new DeleteBucketCommand({ Bucket: bucketParams.Bucket }));
  } catch (err) {
    console.log("Error deleting bucket", err);
  }
};
run();

It's a bit more than you need probably. More info here

CodePudding user response:

I figured out how to upload with POST request(instead of PUT, it's a workaround solution) and here is how:

For your brower:

const formData = new FormData();

Object.keys(signedPost.fields).forEach(key => {
    formData.append(key, signedPost.fields[key]);
});

// You must put the file in the last position otherwise S3 complains:
// "Bucket POST must contain a field named 'key'.  If it is specified, please check the order of the fields."
// If any people who works for Amazon S3 see this error msg, please fix it.. // It's confusing. Using E.g "Put file in the last position" instead.
formData.append("file", file);

fetch(
    signedPost.url,
    {
        method: "POST",
        body: formData,
    }
).then(
    res => console.log(res),
).catch((error) => {
    console.log(error);
    throw error;
});

For your server: Use the generate_presigned_post API instead of generate_presigned_url. E.g:

respone = s3_client.generate_presigned_post(
    Bucket=bucket,
    Key=key,
    ExpiresIn=expires_in_s,
)

My understanding of my original issues so far: For my original "SignatureDoesNotMatch" blocking pre-flight OPTIONS request issue, the post request skip the OPTIONS request. As a result, the post pre-signed workaround it. That's also why $curl PUT works but javascript PUT in Chrome does not work for the exactly same pre-signed url for PUT requests. $curl doesn't run preflight OPTIONS. But for javascript in the Chrome, it runs (a) Send OPTIONS (b) Send PUT and I was blocked in (a). As the discussion in (1), someone fond almost same issue 7 years ago and we still don't have a good answer under that question. With a working post solution, I would let it go instead of burning more time on this. However, It would be great if someone from Amazon could create a ticket for it if necessary.

reference:

  • Related