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:
- (1) here is a very similar issue but it is not exactly same.
- (2) Presigned URLs Example from Amazon
- (3) What's the difference between
generate_presigned_post
andgenerate_presigned_url
?.