Making sure that your images and files are all secure is very important, but sometimes you also want to share them. In this article we're going to learn how to secure out bucket before adding signed urls to allow temporary access to our files by people we trust.

If you want to follow along then you can. You can clone this branch and continue from there:

git clone --single-branch --branch l35-signed-urls

Securing the S3 Bucket and Images

In our serverless.yml file we need to remove the AccessControl: PublicRead field on the bucket config. This will restrict the ability to see the files within the bucket.

Next we need to make sure the images that we're uploading are not public either. If we go to the lambdas/endpoints/imageUpload.js and find the s3 file upload then we can update that functionality. We need to start by adding the import of the top of the file.

import S3 from '../common/S3';

We then need to update the s3 write functionality, making sure to set the ACL to null.

await S3.write(buffer, key, process.env.imageUploadBucket, null, body.mime);

If we save all of this and deploy this then when we upload an image we get an access denied response when we try and access the newly uploaded images.

Adding Signed URLs

With our files secured we need to find a way to give temporary access to people that we need to.

In our common/s3.js file we can add a new method to the object. This is going to be our way of requesting a secure signed URL.

async getSignedURL(bucket, fileName, expriySeconds) {
    return s3Client.getSignedUrl('getObject', {
        Bucket: bucket,
        Key: fileName,
        Expires: expriySeconds,

This function takes three parameters, the bucket, file name and expiry in seconds. This expiry is how long the signed url will be valid for.

Back in the imageUpload.js endpoint we can change the url constant that used to be a template string.

const url = await S3.getSignedURL(process.env.imageUploadBucket, key, 60);

In this case we are passing in an expiry of 60 seconds and we'll test that out later. For now we can deploy the new function code. As we're just updating an existing lambda we can use sls deploy -f imageUpload.


If we go back into out test image upload app, we can upload an image again. We can see that the image is uploaded as we expect and is shown on screen. If we inspect on the image we can copy the image url and paste it into a new tab. You may have noticed that it's a lot longer than the old public urls. This is because it has an access key and secret in the query string parameters. This is how it allows only certain people to access the file.

If you do this within 60s the new tab will contain just the image. If you wait a short while then refresh the page you might get an access denied. If you still see the image then wait another 20s and refresh the page again.

This access denied response shows that the expiry is up and that url is not valid any more. If you want to give permanent access to that file to someone you could have set the expirySeconds to null.