Reputation: 7624
When calling a presigned POST endpoint returned from my node.js server I receive what looks like a valid response:
Note I am setting environment variables to make the POST call easier to repeat.
When calling the presigned URL however I receive a 403 response with SignatureDoesNotMatch error code. I am having a lot of difficulty finding resources about using the POST presigned url. So my questions are as follows:
When performing the post it is my understanding that the call is to POST to the url returned and using form-data include all "fields" in the body with the final key being labelled as file with the file to be uploaded attached. From the above response I have therefore used the following call in postman: Headers:
Body (I have tried both with and without Content-Type included):
However when I make this call I receive a 403 forbidden response and a SignatureDoesNotMatch error code:
Here is the code to generate the presigned URL (using "@aws-sdk/client-s3": "^3.25.0", "@aws-sdk/s3-presigned-post": "^3.25.0" within package.json):
const { createPresignedPost } = require("@aws-sdk/s3-presigned-post");
const { S3Client } = require("@aws-sdk/client-s3");
const s3 = new S3Client({
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
signatureVersion: "v4",
region: "eu-west-2",
});
async function getSignedUrl() {
const params = {
Bucket: "richbits-test",
Key: "d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f",
Conditions: [["eq", "$Content-Type", "image/jpeg"]],
};
console.log(params);
const signedUrl = await createPresignedPost(s3, params);
return signedUrl;
}
I've double checked the bucket and keys, but would appreciate some advice as to how I might move forward or any useful resources which can help me understand the use of these presigned urls better please.
Upvotes: 3
Views: 1631
Reputation: 2374
Woah, the documentation from AWS on these headers is terrible. Here is what I figured out.
createPresignedPost()
is an object with two keys: url
and fields
. The fields
object contains all the form fields and respective values that you must use when submitting the post. You would copy these fields and only these fields to your Postman request.bucket
, key
, policy
, and several fields required to generate the signature. With the JavaScript SDK, these were X-Amz-Algorithm
, X-Amz-Credential
, X-Amz-Date
, and X-Amz-Signature
(though for the Python SDK, these were AWSAccessKeyId
and signature
). These will be returned in the fields
key from createPresignedPost()
, so you do not need to manually generate them.Fields
parameter of createPresignedPost()
is for additional fields that you want to add to the form. The entire set of fields that could possibly be added is listed here. However, the SDK handles many of these for you, particularly the required ones. According to the Python SDK docs, the fields that you could include in Fields
are acl
, Cache-Control
, Content-Type
, Content-Disposition
, Content-Encoding
, Expires
, success_action_redirect
, redirect
, success_action_status
, and x-amz-meta-
.Fields
and Conditions
parameters of createPresignedPost()
need to be synchronized such that any field in Fields
also has a condition in Conditions
and vice versa.Putting this altogether, I believe the problem with your getSignedUrl()
function was that it was missing a Fields
parameter for createPresignedPost()
that had a key-value pair for Content-Type
(since you have a condition for Content-Type
). Without the Content-Type
in the Fields
parameter, the calculated signature would not include the Content-Type
field. If you then left out the Content-Type
field your signature might match, but your policy conditions would fail because it required Content-Type
to be present and equal to image/jpeg
. If you included Content-Type
, then your signature would not match.
Here is could that should work for you.
const { createPresignedPost } = require("@aws-sdk/s3-presigned-post");
const { S3Client } = require("@aws-sdk/client-s3");
const s3 = new S3Client({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
signatureVersion: "v4",
region: "eu-west-2",
});
async function getSignedUrl() {
const params = {
Bucket: "richbits-test",
Key: "d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f",
Conditions: [["eq", "$Content-Type", "image/jpeg"]],
Fields: {"Content-Type": "image/jpeg"},
};
console.log(params);
const signedUrl = await createPresignedPost(s3, params);
return signedUrl;
}
getSignedUrl().then(res => {
console.log(res)
})
The output of this should be
{
Bucket: 'richbits-test',
Key: 'd3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f',
Conditions: [ [ 'eq', '$Content-Type', 'image/jpeg' ] ],
Fields: { 'Content-Type': 'image/jpeg' }
}
{
url: 'https://s3.eu-west-2.amazonaws.com/richbits-test',
fields: {
'Content-Type': 'image/jpeg',
bucket: 'richbits-test',
'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
'X-Amz-Credential': '<YOUR_ACCESS_KEY_ID>/20211005/eu-west-2/s3/aws4_request',
'X-Amz-Date': '20211005T111446Z',
key: 'd3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f',
Policy: '<SOME_BASE64_ENCODED_STRING>',
'X-Amz-Signature': '<THE_SIGNATURE>'
}
}
Then your POST request would include the form fields Content-Type
, bucket
, X-Amz-Algorithm
, X-Amz-Credential
, X-Amz-Date
, Policy
, and X-Amz-Signature
. Here is what an HTML form would look like.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<form action="https://s3.eu-west-2.amazonaws.com/richbits-test" method="post" enctype="multipart/form-data">
Bucket:
<input type="input" name="bucket" value="richbits-test" /><br />
Key to upload:
<input type="input" name="key" value="d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f" /><br />
Content-Type:
<input type="input" name="Content-Type" value="image/jpeg" /><br />
<input type="text" name="X-Amz-Credential" value="<YOUR_ACCESS_KEY_ID>/20211005/eu-west-2/s3/aws4_request" />
<input type="text" name="X-Amz-Date" value="20211005T111446Z" />
<input type="hidden" name="Policy" value="<SOME_BASE64_ENCODED_STRING>" />'
<input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
<input type="hidden" name="X-Amz-Signature" value="<THE_SIGNATURE>" />
File:
<input type="file" name="file" /> <br />
<!-- The elements after this will be ignored -->
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
</html>
As far as debugging this issue, I did not find anything useful from the POST
request to S3. I either got no response or an unauthorized status, without any indication what the problem was.
Upvotes: 5