Differences between PUT and POST S3 signed URLs

PUT and POST URLs are two tools for the same purpose. What are the differences and which one to choose?

6 mins
I have a lot of challenges when it comes to AWS, but I bet your pain points are entirely different than mine. I'd love to hear what keeps you up at night. It would be great to hear from you by filling out this form. Thanks in advance!

Upload signed URLs

Both types of signed URLs fulfill the same goal: provide a serverless-friendly and controlled way for users to upload files directly to S3 buckets. The process is also the same for both as the backend needs to sign the request after validating that the user is authorized then the browser sends the file directly to S3. And finally, both can be used from Javascript equally well.

These are the similarities, but the technical details are different. And these differences determine the amount of fine-tuning each solution supports.

Differences

URL Structure

PUT URLs encode everything in the URL itself as there is nothing else communicated back to the client. This means fewer variables can be set, but on the other hand, they are trivial to use from the browser:

await fetch(url, {
	method: "PUT",
	body: file,
})

POST URLs use multiple fields for different kinds of information. The signing algorithm returns a list of fields along with the URL itself and the client must send those to S3 as well. As this means submitting a form, it is a bit more involved on the client-side:

const formData = new FormData();
formData.append("Content-Type", file.type);
Object.entries(data.fields).forEach(([k, v]) => {
	formData.append(k, v);
});
formData.append("file", file); // must be the last one
await fetch(data.url, {
	method: "POST",
	body: formData,
});

Content Type

For PUT URLs the signing must be done for a specific content type. That means you either hardcode a content type on the backend, for example, application/xml if you want to allow users to upload XML documents, or the client must send the desired content type as part of the signing request. This is usually the case when you want to allow uploading images but don’t want to specify the exact format (png, jpg, etc.).

const contentType = req.query.contentType;
if (!contentType.startsWith("image/")) {
	throw new Error("must be image/");
}
const url = s3.getSignedUrl("putObject", {
	...
	ContentType: contentType,
};

For POST URLs the policy supports a prefix constraint as well as an exact match. For the previous case, you can enforce a prefix and the client can upload any content type that matches that:

const data = s3.createPresignedPost({
	...
	Conditions: [
		["starts-with", "$Content-Type", "image/"],
	]
});

Content length

In case of PUT URLs, you have no control over the size of the uploaded file.

For POST URLs you can set an allowed range in the policy:

const data = s3.createPresignedPost({
	...
	Conditions: [
		["content-length-range", 	0, 1000000], // 0 - 1 MB
	]
});

Metadata

For PUT URLs you cannot add metadata, you only have control over the object key. To add a small amount of information, such as the id of the user who is uploading the object, encode it into the filename:

const url = s3.getSignedUrl("putObject", {
	Key: `${userid}-${getRandomFilename()}`,
});

POST URLs support arbitrary key-value pairs added as object-level metadata. The only caveat is that you need to add that value to the fields also so that the client knows what to set:

const data = s3.createPresignedPost({
	...
	Conditions: [
		["eq", "$x-amz-meta-userid", userid],
	]
});
data.fields["x-amz-meta-userid"] = userid;

And to query these metadata you can use the s3api head-object call:

aws s3api head-object --bucket $BUCKETNAME --key $KEY
{
	...
	"ContentType": "image/jpeg",
	"Metadata": {
		"userid": "user1"
	}
}

Form support

If you don’t want to use Javascript you should use POST URLs. You can set the redirect URL so that clients will be automatically redirected after uploading the file.

Verdict

If both are for the same purpose, which one to choose?

Use POST URLs.

While PUT URLs are simpler to use, they lack some features POST URLs provide. And since constructing a form and submitting from Javascript is just a few lines of code, it shouldn’t be a problem.

Did you know we have a free guide on AWS security basics?

Do you have an AWS account? Of course you do, you've just read an article about AWS. But do you know how to secure it?

As a certified security specialist I'm familiar with most of the security controls AWS offers. I've compiled this guide so you don't have to take a month off to learn all that.

5 quick and easy steps to avoid the rookie mistakes and reduce the risk of costly events down the road.

Download the free guide here:

17 July 2019