DEV Community

Burhan Rampura
Burhan Rampura

Posted on

Uploading Images to S3 Using Pre-Signed URLs and Accessing via CloudFront

This guide will walk you through uploading images to an AWS S3 bucket using pre-signed URLs and retrieving them securely via CloudFront signed URLs.

Image description

Step 1: Create an S3 Bucket

  1. Log in to the AWS Console.
  2. Search for S3 and navigate to the S3 Dashboard.
  3. Click on Create Bucket.
  4. An S3 bucket name must be globally unique
  5. Block all public access (important for security).
  6. Keep all other settings as default and click Create Bucket.

Step 2: Generate Public and Private Keys
Since CloudFront requires a key pair for secure access, generate a public-private key pair using OpenSSL:

openssl genrsa -out private_key.pem 2048
openssl rsa -pubout -in private_key.pem -out public_key.pem
Enter fullscreen mode Exit fullscreen mode

Step 3: Upload the Public Key to CloudFront

  1. Go to CloudFront in the AWS Console.
  2. In the sidebar, navigate to Key Management > Public Keys.
  3. Click Add Public Key and upload the public key generated earlier. Image description

Step 4: Create a Key Group in CloudFront

  1. In Key Management, go to Key Groups.
  2. Click Create Key Group and add the public key you just uploaded. Image description

Note: Select the public key from the dropdown that we created in Step 3.

Step 5: Set Up a CloudFront Distribution

  1. In CloudFront, click Create Distribution.
  2. Choose your S3 bucket as the origin.
  3. In Origin Access, select the recommended control setting, then create a new OAC (Origin Access Control). Image description
  4. Change the Viewer Protocol Policy from 'HTTP and HTTPS' to 'Redirect HTTP to HTTPS'.
  5. Restrict viewer access: Yes, and select the Key Group created earlier.
  6. Keep other settings default and click Create Distribution.

Step 6: Update S3 Bucket Policy and CORS

Go to S3 Bucket > Permissions and add the following Bucket Policy:

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-bucket-name/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::your-account-id:distribution/your-distribution-id"
                }
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Then, configure CORS Policy:

[
    {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["GET", "PUT", "POST"],
        "AllowedOrigins": ["http://yourlocalhost", "https://yourdomain.com"],
        "ExposeHeaders": ["ETag"],
        "MaxAgeSeconds": 3000
    }
]
Enter fullscreen mode Exit fullscreen mode

Step 7: Create a Node.js Backend to Generate Pre-Signed URLs
Initialize a Node.js project:

npm init -y
npm i express dotenv @aws-sdk/client-s3 @aws-sdk/s3-request-presigner @aws-sdk/cloudfront-signer
Enter fullscreen mode Exit fullscreen mode
import express from "express";
import dotenv from "dotenv";
import cors from "cors";

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

import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl as uploads3Object } from "@aws-sdk/s3-request-presigner";
import { getSignedUrl as readCDNImage } from "@aws-sdk/cloudfront-signer";

dotenv.config();

const port = 7000;

// Configure the S3 client with credentials and region
export const s3Client = new S3Client({
  region: "YOUR_REGION",
  credentials: {
    accessKeyId: "YOUR_ACCESS_KEY", // Replace with your AWS access key
    secretAccessKey: "YOUR_SECRET_KEY", // Replace with your AWS secret key
  },
});

const app = express();
app.use(
  cors({
    origin: "http://your_localhost:", // Allow requests from frontend
  })
);

app.use(express.json());

// This private key is used for signing CloudFront URLs
let key = `-----BEGIN PRIVATE KEY-----
YOUR_PRIVATE_KEY_HERE
-----END PRIVATE KEY-----`;

// API route to generate a pre-signed URL for uploading images to S3
app.get("/get-presigned-url", async (req, res) => {
  const { fileName, fileType } = req.query; // Extract filename and file type from request

  const params = {
    Bucket: "YOUR_S3_BUCKET_NAME", // Your S3 bucket name
    Key: `uploads/${fileName}`, // File path inside the bucket
    ContentType: fileType, // The type of file being uploaded
  };

  try {
    const command = new PutObjectCommand(params); // Create an S3 upload command

    // Generate a pre-signed URL valid for 60 seconds to upload image to S3
    const uploadUrl = await uploads3Object(s3Client, command, {
      expiresIn: 60,
    });

    res.status(200).json({
      uploadUrl, // Pre-signed S3 URL for uploading the image
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "Error generating pre-signed URL" });
  }
});

app.get("/get-cdn-url", async (req, res) => {
  const { fileName } = req.query;

 // Generate a signed URL for accessing the image via CloudFront, valid for 1 hour
  try {
     const readImage = readCDNImage({
      url: `https://${process.env.CLOUDFRONT_DOMAIN}/uploads/${fileName}`,
      keyPairId: process.env.CLOUDFRONT_KEY_PAIR_ID, // CloudFront Key Pair ID from .env
      privateKey: key, // Private key for signing the URL
      dateLessThan: new Date(Date.now() + 60 * 60 * 1000), // URL expires in 1 hour
    });

    res.status(200).json({ readImage });
  } catch (error) {
    res
      .status(500)
      .json({ error: "Error generating signed URL" });
  }

});

// Start the Express server
app.listen(port, () => {
  console.log(`Your application running on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

Step 8: Create a React Frontend to Upload Images
Initialize a React project:

npm create vite@latest
cd project-name
npm install
npm install axios
Enter fullscreen mode Exit fullscreen mode
import React, { useState } from "react";
import axios from "axios";

const BASE_URL = "http://localhost:7000";

const app = () => {
  const [file, setFile] = useState("");
  const [imageUrl, setImageUrl] = useState("");
  console.log(file);

  const handleFileChange = (e) => {
    setFile(e.target.files[0]);
  };

  const handleUpload = async () => {
    const { data } = await axios.get(BASE_URL + "/get-presigned-url", {
      params: { fileName: file.name, fileType: file.type },
    });

    await axios.put(data.uploadUrl, file, {
      headers: { "Content-Type": file.type },
    });
  };

  const getImageFromCdn = async () => {
    const { data } = await axios.get(BASE_URL + "/get-cdn-url", {
      params: { fileName: file.name },
    });

    console.log(data.readImage);

    setImageUrl(data.readImage);
  };

  return (
    <>
      <div>
        <input type="file" onChange={handleFileChange} />
        <div>
          <button onClick={handleUpload}>Upload Image to S3</button>
        </div>
      </div>
      <br />
      <div>
        <input type="text" value={file.name} />
        <button onClick={getImageFromCdn}>Get Image from CDN</button>
        <div>
          {imageUrl && <img src={imageUrl} alt="Uploaded" width="200" />}
        </div>
      </div>
    </>
  );
};

export default app;

Enter fullscreen mode Exit fullscreen mode

Conclusion
You have successfully set up a secure system to upload images to S3 using pre-signed URLs and retrieve them via CloudFront signed URLs.

Why Use This Approach?
βœ… Security: No direct public access to S3.
βœ… Performance: CloudFront caches images, reducing load times.
βœ… Cost-Effective: CloudFront reduces the number of direct S3 requests.

Now, your app can securely upload and serve images efficiently!

Top comments (4)

Collapse
 
scorcism profile image
Abhishek Pathak • Edited

great article @burhanuddin_rampura

You can add syntax highlight too

echo "Hello world"
Enter fullscreen mode Exit fullscreen mode

docs.github.com/en/get-started/wri...
Like this.
Overall great

Collapse
 
burhanuddin_rampura profile image
Burhan Rampura • Edited

Thanks a lot! @scorcism πŸ™Œ That's a great suggestionβ€”I’ll look into adding syntax highlighting to improve readability. Appreciate the feedback! 😊

Collapse
 
prathamesh_gawade_16 profile image
prathamesh gawade

Nice one.. :)

Collapse
 
burhanuddin_rampura profile image
Burhan Rampura

Glad you liked it! 😊 Appreciate your support! πŸš€