Presigned URLs embed temporary AWS credentials directly in a URL, allowing any holder of the URL to perform a specific S3 operation for a limited time — without needing AWS credentials of their own. Common uses include sharing downloadable files, allowing direct browser uploads, and granting access to private objects.
Installation
npm install @aws-sdk/s3-request-presigner @aws-sdk/client-s3
Generating a presigned GET URL
Use getSignedUrl with a GetObjectCommand to generate a URL that allows downloading a private S3 object:
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
const client = new S3Client({ region: "us-east-1" });
const command = new GetObjectCommand({ Bucket: "my-bucket", Key: "my-file.pdf" });
const url = await getSignedUrl(client, command, { expiresIn: 3600 });
The expiresIn option sets the URL lifetime in seconds. It defaults to 900 (15 minutes) if not specified.
Generating a presigned PUT URL
Use PutObjectCommand to generate a URL that allows uploading directly to S3 from a browser or another service:
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const client = new S3Client({ region: "us-east-1" });
const command = new PutObjectCommand({ Bucket: "my-bucket", Key: "uploads/file.jpg" });
const url = await getSignedUrl(client, command, { expiresIn: 3600 });
// Share `url` with the client — they can HTTP PUT directly to it
Advanced signing options
Use signableHeaders to require specific headers (such as content-type) to be present and verified in the request:
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const command = new PutObjectCommand({
Bucket: "my-bucket",
Key: "my-key",
ContentType: "image/jpeg",
});
const url = await getSignedUrl(client, command, {
signableHeaders: new Set(["content-type"]),
expiresIn: 3600,
});
By default, x-amz-* headers are hoisted out of the signed URL into the request headers. To force them into the signature (so the upload client must send them), use unhoistableHeaders:
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const command = new PutObjectCommand({
Bucket: "my-bucket",
Key: "my-key",
ChecksumSHA256: sha,
});
const url = await getSignedUrl(client, command, {
expiresIn: 3600,
unhoistableHeaders: new Set(["x-amz-checksum-sha256"]),
});
Overriding the default hoisting behavior
Use hoistableHeaders to explicitly allow certain x-amz-* headers to be hoisted into query string parameters rather than enforced as request headers:
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const command = new PutObjectCommand({
Key: "my-key",
Bucket: "my-bucket",
ServerSideEncryption: "aws:kms",
SSEKMSKeyId: "arn:aws:kms:us-west-2:0000:key/abcd-1234-abcd",
});
const url = await getSignedUrl(new S3Client(), command, {
hoistableHeaders: new Set([
"x-amz-server-side-encryption",
"x-amz-server-side-encryption-aws-kms-key-id",
]),
});
Presigning from an existing request
If you already have a request object, you can presign it using S3RequestPresigner directly:
import { S3RequestPresigner } from "@aws-sdk/s3-request-presigner";
import { Hash } from "@aws-sdk/hash-node";
const signer = new S3RequestPresigner({
region: regionProvider,
credentials: credentialsProvider,
sha256: Hash.bind(null, "sha256"), // In Node.js
// sha256: Sha256 // In browsers (from @aws-crypto/sha256-browser)
});
const presigned = await signer.presign(request);
To reuse an existing S3 client’s configuration:
const signer = new S3RequestPresigner({
...s3Client.config,
});
Presigned POST with @aws-sdk/s3-presigned-post
For browser form-based uploads, use the separate @aws-sdk/s3-presigned-post package instead of a presigned PUT URL. It returns a url and a fields object that you embed in an HTML form or FormData.
Installation
npm install @aws-sdk/s3-presigned-post @aws-sdk/client-s3
Generating a presigned POST
import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
import { S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({ region: "us-west-2" });
const Conditions = [
{ acl: "bucket-owner-full-control" },
{ bucket: "my-bucket" },
["starts-with", "$key", "user/uploads/"],
];
const Fields = {
acl: "bucket-owner-full-control",
};
const { url, fields } = await createPresignedPost(client, {
Bucket: "my-bucket",
Key: "user/uploads/${filename}",
Conditions,
Fields,
Expires: 600, // seconds, default is 3600
});
The Key value can contain ${filename}, which S3 replaces automatically with the name of the uploaded file.
Submitting from Node.js
const { createReadStream } = require("fs");
const FormData = require("form-data");
const form = new FormData();
Object.entries(fields).forEach(([field, value]) => {
form.append(field, value);
});
form.append("file", createReadStream("path/to/a/file"));
form.submit(url, (err, res) => {
// handle the response
});
Limitations
The credentials used to sign a URL must still be valid when the URL is used, not just when it is created. If credentials expire before the expiresIn period ends, the URL will stop working.
- The maximum
expiresIn for URLs signed with temporary credentials (e.g. IAM roles, STS tokens) is the remaining lifetime of those credentials, capped at 12 hours.
- URLs signed with long-term IAM user credentials can have an
expiresIn of up to 7 days.
- Presigned URLs do not support multi-region access points.