Reputation: 1995
I have successfully integrated image upload to S3 using presigned url in App1.
I tried to do the same thing in an App 2, but I keep getting the same error:
403 SignatureDoesNotMatch
FRONTEND:
import React from "react";
import { uploadAdImageToS3BucketAndReturnImageUrl } from "../../../api/calls/data/ads/ads-configuration";
export default function UploadImageTutorial1() {
const onSelectFile = async (event) => {
const file = event.target.files[0];
const { type, name } = file;
// This throws an error
await uploadAdImageToS3BucketAndReturnImageUrl(file, type);
};
return (
<div>
<input type="file" accept="image/*" onChange={onSelectFile} />;
</div>
);
}
api/calls.js
export const uploadAdImageToS3BucketAndReturnImageUrl = async (
imageFile,
typeFile
) => {
try {
const [axios, cancel] = useAxios();
const { data: uploadConfig } = await axios.get(
"/ads/get_image_presigned_url"
);
const saved_image_url = await uploadImageFileToCloud(
uploadConfig,
imageFile,
typeFile
);
return saved_image_url;
} catch (e) {}
};
const uploadImageFileToCloud = async (uploadConfig, imageFile, typeFile) => {
const [axios, cancel] = useAxios();
try {
// This throws 400 error
await axios.put(uploadConfig.url, imageFile, {
headers: {
"Content-Type": typeFile,
"x-amz-acl": "public-read",
},
transformRequest: (data, headers) => {
delete headers.common["Authorization"];
return data;
},
});
} catch (error) {
console.log("🚀 error", error);
}
};
BACKEND:
s3-config.js
// Load the SDK for JavaScript
const { config } = require("dotenv");
config();
var AWS = require("aws-sdk");
const REGION = "xxxxxxxxx"; //e.g. "us-east-1"
AWS.config.update({ region: REGION });
const { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_BUCKET_NAME } =
process.env;
const s3 = new AWS.S3({
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY,
signatureVersion: "v4",
});
var getImageSignedUrl = async function (key) {
return new Promise((resolve, reject) => {
s3.getSignedUrl(
"putObject",
{
Bucket: AWS_BUCKET_NAME,
Key: key,
ContentType: "image/*",
ACL: "public-read",
Expires: 300,
},
(err, url) => {
if (err) {
reject(err);
} else {
resolve(url);
}
}
);
});
};
exports.getImageSignedUrl = getImageSignedUrl;
routes/upload.js
// @route POST api/ads/get_image_presigned_url
// @desc Get image presigned url
// @access Private
router.get("/get_image_presigned_url", (req, res) => {
const key = `images/ad_${Date.now()}.jpeg`;
s3_config
.getImageSignedUrl(key)
.then((url) => {
res.status(200).send({ key, url });
})
.catch((error) => {
res.status(500).send({
message: "There was an error generating pre-signed url.",
});
});
});
When I try to upload an image using the code above, in the frontend, this error gets logged:
When I click on it, this opens in a new page:
<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>XXXXXXXXXXXXXXXXXXXXXx</AWSAccessKeyId>
<StringToSign>
AWS4-XXXX-XXXX 20220526T121600Z 20220526/region/s3/aws4_request
d058c00da90b745bd2xxxxxxxxxxxxxxxxd68b3c537ff
</StringToSign>
<SignatureProvided>
9b372d73xxxxxxxxxxxxxxxxxxxxxx069c90ab7e24193990f1bcd39a0
</SignatureProvided>
<StringToSignBytes>
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 36
54 31 32 31 36 30 30 5a 0a 32 30 32 32 30 35 32 36 2f 65 75 2d 77 65 73 74
2d 33 2f 73 33 2f 61 77 73 34 5f 72 65 71 75 65 73 74 0a 64 30 35 38 63 30
30 64 61 39 30 62 37 34 35 62 64 32 66 66 37 62 35 33 31 39 62 38 61 37 32
33 34 36 37 35 33 65 64 63 31 38 62 63 62 36 38 37 31 65 62 63 39 64 36 38
62 33 63 35 33 37 66 66
</StringToSignBytes>
<CanonicalRequest>
GET /images/image_name.jpeg
Content-Type=image%2F%2A&X-Amz-Algorithm=AWS4-XXXX-XXXXX&X-Amz-Credential=XXXXXXXXXXXX%2F20220526%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20220526T121600Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host%3Bx-amz-acl&x-amz-acl=public-read
host:bucket-name.s3.region.amazonaws.com x-amz-acl:public-read
host;x-amz-acl UNSIGNED-PAYLOAD
</CanonicalRequest>
<CanonicalRequestBytes>
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 30
32 31 38 2e 6a 70 65 67 0a 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3d 69 6d 61
67 65 25 32 46 25 32 41 26 58 2d 41 6d 7a 2d 41 6c 67 6f 72 69 74 68 6d 3d
41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 26 58 2d 41 6d 7a 2d 43 72
65 64 65 6e 74 69 61 6c 3d 41 4b 49 41 52 5a 41 52 52 50 50 49 42 4d 56 45
57 4b 55 57 25 32 46 32 30 32 32 30 35 32 36 25 32 46 65 75 2d 77 65 73 74
2d 33 25 32 46 73 33 25 32 46 61 77 73 34 5f 72 65 71 75 65 73 74 26 58 2d
41 6d 7a 2d 44 61 74 65 3d 32 30 32 32 30 35 32 36 54 31 32 31 36 30 30 5a
26 58 2d 41 6d 7a 2d 45 78 70 69 72 65 73 3d 33 30 30 26 58 2d 41 6d 7a 2d
53 69 67 6e 65 64 48 65 61 64 65 72 73 3d 68 6f 73 74 25 33 42 78 2d 61 6d
7a 2d 61 63 6c 26 78 2d 61 6d 7a 2d 61 63 6c 3d 70 75 62 6c 69 63 2d 72 65
61 64 0a 68 6f 73 74 3a 6c 6f 64 65 65 70 2d 73 74 6f 72 61 67 65 2d 33 2e
73 33 2e 65 75 2d 77 65 73 74 2d 33 2e 61 6d 61 7a 6f 6e 61 77 73 2e 63 6f
6d 0a 78 2d 61 6d 7a 2d 61 63 6c 3a 70 75 62 6c 69 63 2d 72 65 61 64 0a 0a
68 6f 73 74 3b 78 2d 61 6d 7a 2d 61 63 6c 0a 55 4e 53 49 47 4e 45 44 2d 50
41 59 4c 4f 41 44
</CanonicalRequestBytes>
<RequestId>XXXXXXXXXXX</RequestId>
<HostId>
q1cQMi/q0jrkk3SeLd3F8/v/mx62XXXXXXXXXXXXXQbVhCdZCDBNfnluTruGLnkhM=
</HostId>
</Error>;
What’s weird is that SignatureDoesNotMatch is 403 error on their documentation.
I know others have faced the same issue but it seems to be triggered by different causes and I still haven't found what's causing this.
Here, in one answer, this is mentioned:
When making a signed request to S3, AWS checks to make sure that the signature exactly matches the HTTP Header information the browser sent.
This may be what's causing the issue. But, I don't know what exactly.
Upvotes: 0
Views: 3364
Reputation: 1331
In your s3-config.js
file, you have the following:
s3.getSignedUrl(
"putObject",
{
Bucket: AWS_BUCKET_NAME,
Key: key,
ContentType: "image/*",
ACL: "public-read",
Expires: 300,
},
(err, url) => {
if (err) {
reject(err);
} else {
resolve(url);
}
}
);
However, a presigned url for a PUT method
doesn't support wildcard characters in the content type. One solution is to pass the file type into the function, and dynamically set it.
Like so:
var getImageSignedUrl = async function (key, fileType) {
return new Promise((resolve, reject) => {
s3.getSignedUrl(
"putObject",
{
Bucket: AWS_BUCKET_NAME,
Key: key,
ContentType: fileType,
ACL: "public-read",
Expires: 300,
},
(err, url) => {
if (err) {
reject(err);
} else {
resolve(url);
}
}
);
});
};
This requires you to add a fileType parameter
to your GET
method, when your getting your presigned url. Let me know if it helps :)
Note: you must also provide the same content type in your headers, when you make a call to upload your object to the presigned url, but in your code, it looks like it's already there.
Upvotes: 3