Securing AWS S3 file upload with pre-signed URLs
Client-side File Upload Restrictions with AWS S3 Pre-signed URLs
Amazon Simple Storage Service (S3) is a service offered by Amazon Web Services (AWS) that provides object storage through a web service interface. Amazon S3-presigned URLs allow you to securely upload files to an S3 bucket without requiring direct access to your AWS credentials. However, controlling what files can be uploaded using these URLs is crucial for maintaining security and data integrity. There are different strategies for restricting uploads of different files when using a pre-signed URL. Some of these include:
Restricting File Names and Content-Type
Using Policy Conditions
Post Upload File Validation
You can use one of these strategies or combine the three to enhance security and ensure that only authorised files are uploaded to your S3 bucket. We will be focusing solely on restricting file names and content types using the aws-sdk client package.
Installation of the AWS-SDK package (npm, yarn or bower)
npm install aws-sdk
yarn add aws-sdk
bower install aws-sdk-js
Creating your pre-signed URL (Backend)
const { S3Client, CreatePresignedPostCommand } = require('@aws-sdk/client-s3');
async getUploadPreSignedUrl(key, bucket, expiresInSeconds = 1800) {
try {
const contentType = mime.lookup(key) || undefined;
if (!contentType) {
throw new Error('Invalid Content Type');
}
// This is where you set the conditions, eq checks for equality
const conditions = [
{ bucket: bucket },
['eq', '$key', key],
['eq', '$Content-Type', contentType]
];
const params = {
Bucket: bucket,
Key: key,
Conditions: conditions,
Expires: expiresInSeconds,
};
// Generate the presigned post URL using CreatePresignedPostCommand
const command = new CreatePresignedPostCommand(params);
const { url, fields } = await this.s3.send(command);
return { url, fields };
} catch (error) {
console.error("Error generating upload pre-signed URL:", error);
throw error;
}
}
Uploading a file to the Bucket using the pre-signed URL (Frontend)
async function uploadFileToBucket(file, presignedUrlData) {
const formData = new FormData();
// Append fields from presignedUrlData.fields
Object.entries(presignedUrlData.fields).forEach(([key, value]) => {
formData.append(key, value);
});
// Append the file content here
formData.append('file', file);
// This uploads to the S3 bucket
try {
const response = await fetch(presignedUrlData.url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error(`Upload failed with status: ${response.status}`);
}
console.log("Your file uploaded successfully!");
} catch (error) {
console.error("Error uploading file:", error);
}
}
With this, we have been able to enforce a file upload restriction on the client side using pre-signed URLs. This approach ensures an easy, smooth, and secure file upload experience for web engineers.