Uploading Images Securely to AWS S3 Using Presigned URLs

Uploading Images Securely to AWS S3 Using Presigned URLs

Introduction: In the world of web development, uploading images is a common task, whether it's for user avatars, product images, or any other media content. While there are various ways to handle file uploads, ensuring security and efficiency is crucial, especially when dealing with sensitive data like images. In this blog post, we'll explore one of the secure methods for uploading images to Amazon S3 (Simple Storage Service) using pre-signed URLs.

What are Presigned URLs?: Presigned URLs are URLs that grant temporary access to perform specific actions on an object in Amazon S3. These URLs are generated by the S3 service and can be used to upload, download, or delete objects from S3, without requiring the client to have AWS credentials.

Why Use Presigned URLs for Image Uploads?: Presigned URLs offer several advantages when it comes to uploading images securely:

  1. Security: Presigned URLs provide a secure way to upload files to S3 without exposing your AWS credentials to the client-side application.

  2. Control: You can control the expiration time of pre-signed URLs, limiting the window of opportunity for uploading files.

  3. Scalability: By offloading the file upload process to S3, you can reduce the load on your server, resulting in better scalability and performance.

Step-by-Step Guide to Uploading Images Using Presigned URLs:

  1. Generate presigned URL on the Server:

    • On the server side, generate a pre-signed URL using AWS SDK for the desired S3 bucket and object key.

    • Set appropriate conditions such as content type, size limits, and the expiration time for the pre-signed URL.

  2. Send presigned URL to the Client:

    • Once the pre-signed URL is generated, send it to the client-side application.
  3. Upload Image from Client:

    • On the client side, use the pre-signed URL to upload the image directly to S3.

    • Construct a FormData object with the image file and any additional fields required by the presigned URL.

    • Make a POST request to the presigned URL with the FormData.

  4. Handle Upload Completion:

    • Once the upload is complete, the S3 service will return a response with the URL of the uploaded image.

    • Handle this response in your client-side application as needed.

Example Code: Here's an example of how you can implement image uploads using presigned URLs in a JavaScript/Node.js environment:

// server.js

import express from "express";
import { generatePresignedPost } from "./s3Utils"; // Import the function to generate presigned URL

const app = express();

// Route to generate presigned URL
app.get("/generate-presigned-url", async (req, res) => {
  try {
    // Generate presigned URL for uploading image to S3
    const presignedPostData = await generatePresignedPost("YOUR_BUCKET_NAME", "YOUR_OBJECT_KEY");
    res.json(presignedPostData);
  } catch (error) {
    console.error("Error generating presigned URL:", error);
    res.status(500).json({ error: "Failed to generate presigned URL" });
  }
});

// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
// s3Utils.js

import { S3Client } from "@aws-sdk/client-s3";
import { createPresignedPost } from "@aws-sdk/s3-presigned-post";

export const generatePresignedPost = async (bucket, key) => {
  // Create S3 client instance with AWS credentials and region
  const client = new S3Client({
    region: "YOUR_AWS_REGION",
    credentials: {
      accessKeyId: "YOUR_AWS_ACCESS_KEY_ID",
      secretAccessKey: "YOUR_AWS_SECRET_ACCESS_KEY",
    },
  });

  // Define parameters for presigned URL generation
  const params = {
    Bucket: bucket,
    Key: key,
    Conditions: [
      ["content-length-range", 0, 5242880], // Limit content length to 5 MB
      ["starts-with", "$Content-Type", "image/"], // Allow only image uploads
    ],
    Expires: 3600, // Expiration time in seconds (e.g., 1 hour)
  };

  // Generate presigned URL for uploading
  const presignedPost = await createPresignedPost(client, params);
  return presignedPost;
};
// UploadImageForm.js

import React, { useState } from "react";
import axios from "axios";

const UploadImageForm = () => {
  const [file, setFile] = useState(null);
  const [uploadURL, setUploadURL] = useState("");

  // Function to handle file selection
  const handleFileChange = (event) => {
    setFile(event.target.files[0]);
  };

  // Function to submit the form
  const handleSubmit = async (event) => {
    event.preventDefault();
    if (!file) return;

    try {
      // Request presigned URL from server
      const response = await axios.get("/generate-presigned-url");
      const presignedPostData = response.data;

      // Upload file to S3 using presigned URL
      await uploadFileToS3(presignedPostData, file);

      // Get the uploaded file URL
      const fileURL = `${presignedPostData.url}/${presignedPostData.fields.key}`;
      setUploadURL(fileURL);
    } catch (error) {
      console.error("Error uploading file:", error);
    }
  };

  // Function to upload file to S3 using presigned URL
  const uploadFileToS3 = async (presignedPostData, file) => {
    const formData = new FormData();
    Object.keys(presignedPostData.fields).forEach((key) => {
      formData.append(key, presignedPostData.fields[key]);
    });
    formData.append("file", file);

    const config = {
      headers: {
        "Content-Type": "multipart/form-data",
      },
    };

    await axios.post(presignedPostData.url, formData, config);
  };

  return (
    <div>
      <h2>Upload Image to S3</h2>
      <form onSubmit={handleSubmit}>
        <input type="file" onChange={handleFileChange} />
        <button type="submit">Upload</button>
      </form>
      {uploadURL && (
        <div>
          <h3>Uploaded Image URL:</h3>
          <img src={uploadURL} alt="Uploaded" />
        </div>
      )}
    </div>
  );
};

export default UploadImageForm;

Conclusion:

Uploading images securely to Amazon S3 using presigned URLs is a powerful technique that offers security, control, and scalability. By following the steps outlined in this blog post and leveraging the AWS SDK and client-side HTTP clients like Axios, you can seamlessly integrate image uploads into your web applications while ensuring the safety and integrity of your data. So, next time you need to handle image uploads, consider using presigned URLs for a secure and efficient solution.