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?
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
]
});
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.