Learn how to use AWS S3 pre-signed URLs to securely share files from a Node.js app. Pre-signed URLs enable temporary, controlled access to files in your S3 bucket without exposing them publicly. Perfect for secure file sharing, this guide walks through setup, code, and best practices.

AWS S3 (Simple Storage Service) is a popular choice for securely storing files in the cloud. However, in many scenarios, you might want to grant temporary access to files in your S3 bucket without exposing them publicly. For this, AWS offers a powerful feature called pre-signed URLs. In this guide, we’ll learn how to use pre-signed URLs to securely share files stored in S3 using Node.js.
If you're new to uploading files to S3, you might want to start with my previous tech blog post on setting up file uploads to S3 using Node.js and Express. This post will build on that foundation, adding secure, temporary access to those files via pre-signed URLs.
A pre-signed URL is a temporary, unique URL that grants time-limited access to a specific file in an S3 bucket. Pre-signed URLs are ideal for cases where you need to share a file temporarily or restrict access without exposing the entire bucket to the public.
Pre-signed URLs are generated by the server and can be set to expire within a specified time, such as 60 seconds, 10 minutes, or several hours. The URL allows authorized access to the object for anyone with the link until it expires.
To follow along, make sure you have:
To work with S3, we’ll use AWS SDK’s S3Client and set up our connection parameters. Create a s3Client.js file to initialize the S3 client:
dotenv.config();
export const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});In order to upload and manage files in memory, we use multer, a Node.js middleware. Here’s a quick setup for using multer with memory storage:
const storage = multer.memoryStorage();
export const upload = multer({ storage });Now, files will be stored in memory, allowing us to upload them directly to S3 or generate pre-signed URLs without saving them on the server.
Our main focus is generating a pre-signed URL to allow secure, temporary access to a file stored in S3. Let’s create a controller function that generates a pre-signed URL for an uploaded file.
Here’s how the getPresignedAttachmentUrl function works:
export const getPresignedAttachmentUrl = async (req, res) => {
const { attachmentId } = req.params;
const userId = req.user.userId;
try {
const attachment = await AttachmentModel.findById(attachmentId);
if (!attachment) {
return res.status(404).json({ message: "Attachment not found" });
}
// Check if user has access to the attachment
const note = await NoteModel.findOne({ user: userId, attachments: attachmentId });
const sharingRecord = await SharingModel.findOne({ sharedWithIds: userId });
if (!note && !sharingRecord) {
return res.status(403).json({ message: "Access denied" });
}
// Extract file key from S3 URL
const fileKey = attachment.s3Url.split(".com/")[1];
// Generate pre-signed URL
const command = new GetObjectCommand({
Bucket: process.env.AWS_BUCKET_NAME,
Key: fileKey,
});
const signedUrl = await getSignedUrl(s3, command, { expiresIn: 60 });
res.status(200).json({ fileUrl: signedUrl });
} catch (error) {
console.error("Error generating pre-signed URL:", error);
res.status(500).json({ message: "Error generating pre-signed URL", error });
}
};Explanation of the Code
getSignedUrl, we create a pre-signed URL with a 60-second expiration.Next, let’s set up an Express route to handle requests for pre-signed URLs. In your main server file (index.js), create the route, and link it to the getPresignedAttachmentUrl controller.
const app = express();
const PORT = process.env.PORT || 3000;
// Route to get a pre-signed URL for file access
app.get("/attachments/:attachmentId/url", getPresignedAttachmentUrl);
app.listen(PORT, () => {
console.log(Server running on port ${PORT});
});To test this endpoint:
node index.js.http://localhost:3000/attachments/{attachmentId}/url with a valid attachmentId.{
"fileUrl": "https://your-bucket.s3.your-region.amazonaws.com/your-file-key?X-Amz-Expires=60&X-Amz-Signature=abc123..."
}Clicking or navigating to this fileUrl in a browser will allow access to the file for 60 seconds.
Pre-signed URLs are a powerful feature for securely sharing files from AWS S3. By generating URLs that expire after a certain time, you can grant temporary access to your files while keeping your S3 bucket private. With both uploading and secure access in place, your application can handle file management efficiently and securely. That's it for this article! See ya 👋
New tutorials, book updates, and behind-the-scenes notes from the studio. No schedule, no spam.