AWS Developer Blog

Generate a presigned URL in modular AWS SDK for JavaScript

On December 15th, 2020, we announced the general availability of the AWS SDK for JavaScript, version 3 (v3). This blog shows you how to generate a presigned URL for an Amazon S3 bucket using the modular AWS SDK for JavaScript.

Motivation

A presigned URL gives you access to the object identified in the URL, provided that the creator of the presigned URL has permissions to access that object. That is, if you receive a presigned URL to download an object, you can download the object only if the creator of the presigned URL has the necessary permissions to download that object.

The presigned URLs are useful if you want your customers to be able to operate on a specific object in your bucket, but you don’t want to give them certain permissions or access to AWS credentials at all.

To learn more about this feature, go to S3 service documentation.

Generate a presigned URL for GetObject

This section shows you how can generate a presigned URL that users can use to download objects in your bucket.

With Client and Command

Using the @aws-sdk/s3-request-presigner package, you can generate presigned URL with S3 client and command. The presigned URL expires in 15 minutes by default. You can specify how long (in seconds) your URL stays valid for by passing expiresIn parameter.

JavaScript Example:

const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
// ...
const client = new S3Client(clientParams);
const command = new GetObjectCommand(getObjectParams);
const url = await getSignedUrl(client, command, { expiresIn: 3600 });

ES6 Example:

import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
// ...
const client = new S3Client(clientParams);
const command = new GetObjectCommand(getObjectParams);
const url = await getSignedUrl(client, command, { expiresIn: 3600 });

Anyone with access to the URL can view the object, as long as the user that generates the URL has the required permissions.

With Object URL

If you have an existing object URL, you can generate a presigned URL without importing S3 client package.

ES6 Example

import { HttpRequest } from "@aws-sdk/protocol-http";
import { S3RequestPresigner } from "@aws-sdk/s3-request-presigner";
import { parseUrl } from "@aws-sdk/url-parser";
import { Sha256 } from "@aws-crypto/sha256-browser";
import { Hash } from "@aws-sdk/hash-node";
import { formatUrl } from "@aws-sdk/util-format-url";
// ...
const s3ObjectUrl = parseUrl(`https://${bucket}.s3.${region}.amazonaws.com/${key}`);
const presigner = new S3RequestPresigner({
    credentials,
    region,
    sha256: Hash.bind(null, "sha256"), // In Node.js
    //sha256: Sha256 // In browsers
});
// Create a GET request from S3 url.
const url = await presigner.presign(new HttpRequest(s3ObjectUrl));
console.log("PRESIGNED URL: ", formatUrl(url));

Generate a presigned URL for PutObject

Similarly, you can generate presigned URL to allow users to upload objects to your bucket.

With Client and Command

ES6 Example:

// ...
import { S3Client, PutObjectCommand} from "@aws-sdk/client-s3";
// ...
const command = new PutObjectCommand(putObjectParams);
const url = await getSignedUrl(client, command, { expiresIn: 3600 });

With Object URL

ES6 Example

// ...
// Create a PUT request from S3 url.
const url = await presigner.presign(new HttpRequest({...s3ObjectUrl, method: "PUT"}));
console.log("PRESIGNED URL: ", formatUrl(url));

Upload Payload

You can send the payload with the HTTP client of your choice. An example with the fetch client in browser:

const file = document.getElementById("fileItem").files[0];
const response = await fetch(formatUrl(url), { method: "PUT", body: file });

The payload to be uploaded with presigned URL must have known length. In the example above, the file implicitly contains the size of the payload. If your payload doesn’t have known length (e.g. ReadableStream), you need to send the Content-Length header with the request. Here’s an example of sending streaming payload in Node.js using node-fetch client.

import fetch from "node-fetch";
import { createReadStream, statSync } from "fs";
//...
const payload = createReadStream(filePath);
const response = await fetch(presignedUrl, {
    method: "PUT",
    body: payload,
    headers: {
         "Content-Length": statSync(filePath).size
    }
});

Caveats

Send headers explicitly when requests with server-side encryption configuration

If your request specifies server-side encryption (SSE*) configurations, you need to send corresponding headers along with the presigned URL, because of S3 requirement. Here’s an example of setting server-side encryption headers when uploading with presigned URL.

const response = await fetch(presignedUrl, {
    method: "PUT",
    body: file 
    headers: {
        "x-amz-server-side-encryption-aws-kms-key-id": "KEY_ID",
        "x-amz-server-side-encryption-context": "arn:aws:s3:::bucket_ARN"
    }
});

For more information, please go to S3 SSE reference.

URL may expire earlier if signed with temporary credentials

A presigned URL can be valid for up to 7 days. You can use temporary credentials to presign a request as long as the associated role is configured with corresponding permissions. However, the presigned request will expire when the temporary credentials used to sign is expired, even when the expiresIn is configured at a later time.

Feedback

We value your feedback, so please tell us what you like and don’t like by opening an issue on GitHub.

Allan Zheng

Allan Zheng

Allan is maintainer of AWS SDK for JavaScript in Node.js and browser. He builds tools helping users navigating the AWS. Find him on GitHub @AllanZhengYP.