Secure File Uploads in Next.js with AWS S3 Presigned URLs

User avatar placeholder
Written by Tamzid Ahmed

June 1, 2026

Secure file uploads are critical for any web application handling user data. In Next.js, directly exposing AWS credentials on the client side can lead to severe security breaches. Using AWS S3 presigned URLs provides a secure way to handle uploads without compromising your infrastructure.

Why Presigned URLs Are Essential for Secure File Uploads

Traditional file upload methods often require client-side access to AWS credentials, which is a major security risk. AWS S3 presigned URLs solve this by generating temporary, limited-access URLs on the server. These URLs grant restricted permissions for a short time, eliminating the need to expose long-term credentials.

Without presigned URLs, attackers could exploit exposed credentials to access or delete your S3 bucket contents. By using server-side signing, you maintain strict control over upload permissions and minimize exposure risks.

Step-by-Step Implementation in Next.js

Setting Up AWS S3 and IAM Roles

First, create an S3 bucket with strict access policies. Then configure an IAM role with minimal permissions:

  • Only allow s3:PutObject for the specific bucket
  • Restrict to a specific folder path (e.g., uploads/)
  • Set a short expiration time for presigned URLs (e.g., 15 minutes)

Creating the Presigned URL in Next.js API Route

Use the AWS SDK v3 in a Next.js API route to generate the presigned URL:

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

export default async function handler(req, res) {
  if (req.method !== 'POST') return res.status(405).end();

  const s3 = new S3Client({ region: 'us-east-1' });
  const command = new PutObjectCommand({
    Bucket: 'your-bucket-name',
    Key: `uploads/${Date.now()}-${req.body.fileName}`,
  });

  const url = await getSignedUrl(s3, command, { expiresIn: 900 });
  res.status(200).json({ url });
}

Client-Side Upload with React

On the frontend, fetch the presigned URL and upload the file directly to S3:

const uploadFile = async (file) => {
  const response = await fetch('/api/upload', {
    method: 'POST',
    body: JSON.stringify({ fileName: file.name }),
  });
  const { url } = await response.json();

  await fetch(url, {
    method: 'PUT',
    body: file,
    headers: { 'Content-Type': file.type },
  });
};

Security Best Practices and Common Pitfalls

Even with presigned URLs, security requires additional layers:

  • Validate file types and sizes on the server before generating the URL
  • Use S3 bucket policies to block public access and enforce encryption
  • Never allow client-side validation alone—always validate on the server
  • Rotate AWS credentials regularly and use environment variables for secrets

Common mistakes include using overly permissive IAM roles or forgetting to validate file contents. Always test your implementation with security tools like AWS IAM Access Analyzer.

Testing and Monitoring Your Implementation

After deployment, monitor uploads using AWS CloudTrail logs. Test edge cases like large files, invalid types, and expired URLs. Use tools like Postman to simulate malicious requests and verify security controls.

Conclusion

Implementing secure file uploads in Next.js with AWS S3 presigned URLs is essential for protecting your infrastructure and user data. By handling signing on the server, validating inputs, and enforcing strict IAM policies, you eliminate credential exposure risks. Always prioritize server-side validation and regularly audit your S3 bucket policies. Start implementing this today to build more secure applications.

Leave a Comment