Effortlessly Upload Files to AWS S3 Bucket via React with Pre-signed URLs
Written on
Introduction
When uploading files to an AWS S3 bucket, it's essential to prioritize security. While using the AWS SDK for direct uploads is common, it can potentially expose your AWS credentials in the code. This guide will demonstrate a more secure method to upload files from a React application to an S3 bucket using pre-signed URLs.
Utilizing pre-signed URLs for file uploads enhances security, scalability, and performance. This method allows for direct uploads with restricted permissions and defined expiration times, minimizing server load and costs. It also enhances the user experience while providing flexibility for managing file uploads.
Step 1: Create an S3 Bucket
Begin by creating an S3 bucket in your AWS account, ensuring that the bucket name is unique.
Step 2: Add CORS Policy to S3 Bucket
To enable your React front-end to upload files to the S3 bucket, you'll need to implement a CORS (Cross-Origin Resource Sharing) policy. This policy specifies which origins are permitted to access the bucket and the allowed HTTP methods.
Step 3: Create Lambda Function
Next, set up a Lambda function to generate pre-signed URLs that facilitate the upload of files to the S3 bucket. In this example, we will focus on uploading .png files, but you can adapt this for other file types. The function will be called by your React application to receive a pre-signed URL, granting temporary access for file uploads.
Here is a sample code snippet for the Lambda function that defines the upload bucket and generates a unique key for the object. This function uses s3.generate_presigned_url() with the PUT method and a one-hour expiration time. You can customize the settings as needed.
import json
import boto3
import uuid
# Configure S3 client
s3 = boto3.client('s3')
def lambda_handler(event, context):
# Get the bucket name from an environment variable
bucket_name = 'helela-presigned-url-upload'
key = f'{uuid.uuid4()}.png' # Generate a unique key for the object
# Generate pre-signed URL
presigned_url = s3.generate_presigned_url(
ClientMethod='put_object',
Params={
'Bucket': bucket_name,
'Key': key,
'ContentType': 'application/png', # Set the Content-Type for .png
},
ExpiresIn=3600, # URL expiration time set to 1 hour
HttpMethod='PUT' # Only allow PUT requests on the URL
)
# Construct the response
response = {
'statusCode': 200,
'headers': {
'Access-Control-Allow-Origin': 'http://localhost:3000'},
'body': json.dumps({
'presignedUrl': presigned_url,
'key': key
})
}
return response
Step 4: Add PutObject Policy to Lambda Function’s IAM Role
To permit the Lambda function to upload files to the S3 bucket on behalf of the client, you must add a PutObject policy to the IAM role associated with the Lambda function. This policy grants the necessary permissions for the pre-signed URL to write objects to the S3 bucket.
Navigate to the Configuration/Permissions section of your Lambda function and click on the name of the Execution role to access your IAM role console.
Click on Add permissions and then Create inline policy.
Select JSON and paste your custom policy, ensuring to use your own bucket ARN from the S3 bucket.
Assign a unique policy name and create the policy.
Step 5: Add API Gateway Trigger
Integrating API Gateway as a trigger for your Lambda function to generate pre-signed URLs enhances security, scalability, customization, and rate limiting, while ensuring seamless Lambda integration.
Set up API Gateway as a trigger for your Lambda function, enabling your React application to invoke the Lambda function over HTTP and retrieve pre-signed URLs for file uploads.
Go to API Gateway and create an HTTP API.
To link this API as a trigger to the Lambda function, enter the Lambda function’s ARN.
Change the method from ANY to GET to limit the API to only GET requests.
Select the $default stage on the next page and click Next.
After creating the API, set the CORS policy to only allow requests from http://localhost:3000 (or your application’s host).
Confirm the creation of the API Gateway Lambda trigger on the Lambda function’s page.
Step 6: Test the Upload from a React Front-end
Lastly, incorporate the file upload feature into your React application. Utilize the Axios library to send HTTP requests to the API Gateway endpoint, which will call the Lambda function to return a pre-signed URL. You can then use this URL to upload the file directly to the S3 bucket from the client side.
To facilitate requests for the pre-signed URL and upload the file using it, install the Axios library:
npm install axios
Modify App.js and insert the following code, ensuring to replace API_ENDPOINT with your actual API endpoint.
import React, { useState } from "react";
import "./App.css";
import axios from "axios";
function App() {
const [selectedFile, setSelectedFile] = useState(null);
const [uploadProgress, setUploadProgress] = useState(0);
const handleFileChange = (event) => {
setSelectedFile(event.target.files[0]);};
// API Gateway URL to invoke function for generating presigned URL
const API_ENDPOINT = "https://your-api-endpoint-here/route";
// Function to generate the presigned URL
const getPresignedUrl = async () => {
// GET request: presigned URL
const response = await axios({
method: "GET",
url: API_ENDPOINT,
});
const presignedUrl = response.data.presignedUrl;
console.log(presignedUrl);
return presignedUrl;
};
// Function to upload the selected file using the generated presigned URL
const uploadToPresignedUrl = async (presignedUrl) => {
// Upload file to pre-signed URL
const uploadResponse = await axios.put(presignedUrl, selectedFile, {
headers: {
"Content-Type": "application/png",},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
setUploadProgress(percentCompleted);
console.log(Upload Progress: ${percentCompleted}%);
},
});
console.log(uploadResponse);
};
// Function to orchestrate the upload process
const handleUpload = async () => {
try {
// Ensure a file is selected
if (!selectedFile) {
console.error("No file selected.");
return;
}
const presignedUrl = await getPresignedUrl();
uploadToPresignedUrl(presignedUrl);
} catch (error) {
// Handle error
console.error("Error uploading file:", error);
}
};
return (
<div className="App">
<h1>File Selection Component</h1>
<input type="file" onChange={handleFileChange} />
<button onClick={handleUpload}>Upload</button>
</div>
);
}
export default App;
Run and test your application:
npm start
Conclusion
Following these steps allows you to securely upload files to an AWS S3 bucket from a React application using pre-signed URLs. This method reduces the security risks associated with direct uploads to S3 and provides a scalable, efficient solution for managing file uploads in your application.