Generating pre-signed URLs in NodeJS
1. What are pre-signed URLs?
In AWS S3, all objects (files, images etc.) are private by default, which means that only the account (object) owner can access them.
Sometimes it’s necessary to share these objects with external users. It’s definitely not a good idea to give out our security credentials, so AWS came up with the concept of pre-signed URLs.
Pre-signed URLs provide the users of the application with access to the objects in our AWS S3 bucket. Users click on the URL, and they can read (download) the object, or can write (upload) a new object to the bucket, while other objects or buckets remain secure.
The URL expires after a set period of time, which can be set in the params
object.
Typical use cases:
- A service provider allow clients to download the monthly invoices after logging in to their application.
- Users can upload photos and show them to everyone they are connected to.
2. Pre-requisites
To create a pre-signed URL, one needs to have an AWS account and credentials for programmatic access to make the following code work.
3. The getSignedUrl method
Both the up- and download operations can be done using the getSignedUrl
method of the SDK for Node.js.
The method accepts the name of the operation method as a string and the parameters of the operation method.
For example, for a download URL we need to specify the getObject
method as a string and add the parameters of this method. Normally, putObject
can be used to upload objects to S3, so this method needs to be specified with the relevant parameters when we want to create an upload URL.
More information on up- and downloading objects from S3 can be found in this post.
As such, the code for both situations will be very similar.
The generation of the pre-signed URL occurs locally, and the account owner (we) will sign it using our credentials, more accurately, the secret access key, which acts as a private key. As a result, the getSignedUrl
method doesn’t have the option to chain the promise
method to it.
3.1. Set up the project
Create a folder with a pleasant name, and run npm init -y
. The next step is to install the SDK by running npm install aws-sdk
.
We’ll work with the Architecting for the Cloud - AWS Best Practices document, so let’s upload it first to S3 either through the console, or following the steps in the last post.
The file needs to be saved in a bucket of a unique name, so the name of the bucket in your example should be different from s3-upload-and-download-suad123
.
3.2. Generate a pre-signed URL for download
In a file called getObject-pre-signed-url.js
, we can have the following code:
const AWS = require('aws-sdk')
const s3 = new AWS.S3({
region: 'us-west-2',
signatureVersion: 'v4',
})
const BUCKET_NAME = 's3-upload-and-download-suad123'
const getSignedUrlForDownload = async () => {
const params = {
Bucket: BUCKET_NAME,
Key: 'AWS_Cloud_Best_Practices.pdf',
Expires: 60,
}
const url = await new Promise((resolve, reject) => {
s3.getSignedUrl('getObject', params, (err, url) => {
if (err) reject(err)
resolve(url)
})
})
return url
}
getSignedUrlForDownload()
.then((url) => {
console.log(url)
}).catch((e) => {
console.log(e)
})
We specifiy the region
and signatureVersion
at a service level.
The getSignedUrlForDownload
function contains the parameters for the getObject
method we use for downloading objects from S3.
There’s one additional key in the params
object, which is specific to getSignedUrl
. The Expires
key specifies the validity of the link in seconds. In our case, the download link is valid for 1 minute.
As it was stated above, it’s not possible to attach the promise
built-in AWS method to getSignedUrl
. Because it accepts a callback as the third argument, we can wrap getSignedUrl
in a Promise.
When an error occurs, the promise is reject
ed with the error. On the happy side, if everything goes well, we resolve
the promise with the URL.
Because we work with a promise here, we can await
it inside getSignedUrlForDownload
, but it needs to be an async function.
This way, getSignedUrlForDownload
will return a promise (every async
function returns a promise), so we can call it with a then
and catch
.
3.3. The URL
In the terminal, run node
with the name of the file, i.e. node getObject-pre-signed-url.js
, and the URL should be seen in the console:
https://s3-upload-and-download-suad123.s3.us-west-2.amazonaws.com/AWS_Cloud_Best_Practices.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=YOUR_ACCESS_KEY_us-west-2%2Fs3%2Faws4_request&X-Amz-Date=20190527T205311Z&X-Amz-Expires=60&X-Amz-Signature=4eecbf02f9ff51b9a84144fa26751aa3b7c891058f5532533440b3a080bbd0ee&X-Amz-SignedHeaders=host
As it can be seen, the URL contains the name of the bucket and the region with the name of the object we want to download.
The URL also comes with some interesting characters as query parameters. It has the encryption algorithm (SHA-256
), the access key of the account owner, the date, the expiration of the link and the signature of the owner of the object.
3.4. Upload link
The upload is very similar to the download, the difference is that we need to use the putObject
method inside getSignedUrl
:
const getSignedUrlForUpload = async () => {
const params = {
Bucket: BUCKET_NAME,
Key: 'AWS_Cloud_Best_Practices.pdf',
Expires: 60,
ContentType: 'application/pdf',
}
const url = await new Promise((resolve, reject) => {
s3.getSignedUrl('putObject', params, (err, url) => {
if (err) reject(err)
resolve(url)
})
})
return url
}
uploadFileToS3()
.then((res) => {
console.log(res)
}).catch((e) => {
console.log(e)
})
Here we also specify the ContentType
key in the params
object, which is application/pdf
.
Calling the method will result in the URL, which can then be pasted in Postman, for example. Ensure that you choose the PUT
method, and the body (the file to upload) should be in binary format.
Depending on the size, the file should be up in S3 within a few seconds.
4. Conclusion
Objects in S3 are private by default. In some cases, access need to be provided to these objects either in a form of a download or upload.
Pre-signed URLs provide a safe way to access objects stored in S3. For both upload and download, we use the getSignedUrl
method, which generates a one-time-only link to the given resource.
Thanks for reading, and see you next time.