by philwonski  • January 28, 2022

S3 Generate Pre-Signed URL, an Example for Downloads

So, you’ve got some private objects in an S3 bucket, and you want to generate a pre-signed URL so you can access a file? This quick example shows you how you can generate the URL on-the-fly using the wicked API integration platform Pipedream.

S3 Pre-Signed URL Example: Viewing a Private Image in an Email

I built a web form for a customer recently, and it required the user to upload an image of a receipt. I got the image into S3 by:

  • Base64 encoding the image on the frontend, into a JSON object.
  • Posting the JSON object to API Gateway.
  • Using a Lambda and buffer.from to write the image to S3.

Sweet. But now how do I let my customer view the receipt images?

In other words, when a form is submitted and my customer gets an email, how can I embed the receipt image in that email if the bucket isn’t public?

<img src="https://mybucket.s3.us-east-1.amazonaws.com/myreceipt.jpg"/>

//except oh wait, I'll get s3 access denied errors without a pre-signed URL! 

Generating pre-signed URLs with S3 API is the answer here. It may sound intimidating, but it’s easier than you think thanks to a great tool called Pipedream.

If you haven’t heard of Pipedream, it’s a Zapier alternative designed for developers. We’ll use Pipedream to interact with the S3 API and generate the pre-signed URL.

Pipedream workflow diagram to request and return a s3 pre-signed url.
Using Pipedream to generate S3 Pre-signed URLs and return them anywhere we want.

Get Pre-signed URL in S3 with Node.js

Here’s the steps to get this going:

1. Set up an Endpoint in Pipedream to receive the key name for the object.

In Pipedream, I went ahead started a new Workflow. To kick off the Workflow, I set up an endpoint to receive a JSON payload containing the bits of information I need for this task. In my case, the form actually lets the user designate whether or not they have a receipt available to upload, so I’ll be accepting two values:

{
"has_receipt": "yes",
"imagename": "likeABC.jpg"
}
2. Add your AWS account (an IAM user) to Pipedream.

In Pipedream, navigate to the “Accounts” tab in the left panel. Here you’ll need to click “Connect an App” in the top right to add AWS IAM credentials. Make sure you IAM user has full S3 permissions on this bucket.

3. Back in your Workflow, add an AWS Step.

Back in your workflow, click the plus sign on the bottom to add a new step, and search for and select “AWS.” Then you want the one that says “Use any AWS API.”

Selecting the AWS API in pipedream to interact with the s3 presigned url api method
Pipedream helps us with some boilerplate code to authenticate our IAM user account with AWS. Now we can use any AWS API, in this case the S3 API to generate a pre-signed URL for our object.

Here’s the code I used in my AWS step. The first two lines will be provided by Pipedream to help you authenticate with the S3 API:

const AWS = require("aws-sdk")
const { accessKeyId, secretAccessKey } = auths.aws

const expiresin = 600000;
const mybucket = 'YOUR_BUCKET_NAME_HERE';
const mykey = steps.trigger.event.body.imagename

const s3 = new AWS.S3({
  accessKeyId, 
  secretAccessKey,
  region: 'us-east-1',
})

if (steps.trigger.event.body.has_receipt == 'yes') {
const url = s3.getSignedUrl('getObject', {
    Bucket: mybucket,
    Key: mykey,
    Expires: expiresin
})

console.log('url is: ' + url)
return(url);
} else {
  return('https://YOUR_BUCKET_NAME_HERE.s3.amazonaws.com/noreceipt.jpg');
}

That’s it! This step will now return a pre-signed URL for the object identified in the request (imagename).

Pre-signed URL Expiration and Other Notes

To walk through what I did in the code above, note the following particulars:

A. Pre-signed URL max expiration is one week.

For security, we can’t have these URLs lasting forever. AWS only allows them to last a week, which is a little over 600,000 seconds. So that’s why I used 600,000 in the expiresin variable.

B. The s3.getSignedUrl SDK method.

The reason this code was so easy to create is because the AWS SDK provides a super-handy method for generating the pre-signed URL. It’s called s3.getSignedUrl, and it doesn’t require anything more than the parameters listed above in the code (the bucket, the key name, and when you want it to expire).

C. If – else logic, which you may not need for your purposes.

Keep in mind that this is an actual example from out in the wild, and you may not need the logic I used to determine whether or not the user submitted a receipt. For my app, I had a placeholder image in the bucket called noreceipt.jpg. This way the subsequent steps in my workflow always have an image URL to work with, and my customer gets a very obvious notice in her email that this user didn’t provide their receipt.

If you don’t need this logic, just use this bit below and remove the if-else stuff:

const url = s3.getSignedUrl('getObject', {
    Bucket: mybucket,
    Key: mykey,
    Expires: expiresin
})

console.log('url is: ' + url)
return(url);
D. Continuing the Workflow with the returned URL.

So the Workflow step returned a pre-signed URL as expected. Great. Now what?

Well, thanks to the beauty of Pipedream, I can do anything I want with the URL now that I have it. In my case, I went ahead and created another Workflow Step in Node.js, for the purpose of composing an email to my customer with a custom notification containing other form data along with the image.

I’ll go through sending emails from Pipedream using Amazon SES in a separate tutorial… but note that I embedded the image of the receipt in the email as follows. See the img tag in line 3? That’s how you reference the output of a Workflow Step in Pipedream (you can also create exports from a step using this as described here).

Anyway, you’ll want to do something similar using steps.aws.$return_value with whatever your next step may be. Here’s how I use the return value in composing the HTML for the email in my next step:

const htmlemail_part1 = "<html><body><h2>New Customer Troubleshoot Request</h2>";
const htmlemail_part2 = "<p>" + other.workflow.details + "</p>";
const htmlemail_part3 = "</p><p> <img src=\"" + steps.aws.$return_value + "\"/></p>";
const email_body = htmlemail_part1 + htmlemail_part2 + htmlemail_part3;

Nice!


If you liked this post, follow me on Twitter @philwonski so you know when I post similar content. You can see past posts by me here.

There’s also lots of great (and less esoteric) content being produced by our team on a regular basis — be sure to find us on LinkedIn, and sign up to our newsletter in the footer of this page.

Unlock cover photo by FLY:D on Unsplash.