Thursday, July 24, 2025

How to Upload Files to S3 Using Presigned URLs (Securely)

s3

Why Clients Fail at Presigned Uploads

Common mistakes developers encounter:

  • Clients forget CORS and see 403 or OPTIONS errors.

  • Uploads succeed—but stale stale URLs generate later 403s.

  • Large file uploads break due to expiration or wrong content-length.

  • URLs leak via caching and can be replayed.

Let’s tackle a solid use case: uploading user profile images from a React client, sized up to 5 MB, using presigned PUT URLs, with secure, short-lived tokens and backend validation of file type/size before URL issuance.

Step 1: Backend Endpoint to Generate Presigned URL

// presign.js
import express from 'express';
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import jwt from 'jsonwebtoken';

const router = express.Router();
const s3 = new S3Client({ region: "us-east-1" });

router.post('/sign-upload', async (req, res) => {
  const { token, fileName, contentType, fileSize } = req.body;
  const user = jwt.verify(token, process.env.JWT_SECRET);
  if (!user) return res.status(401).send("Unauthorized");

  if (fileSize > 5 * 1024 * 1024)
    return res.status(400).send("File too large");

  if (!/^(image\/jpeg|image\/png)$/.test(contentType))
    return res.status(400).send("Invalid file type");

  const key = `uploads/${user.id}/${Date.now()}-${fileName}`;
  const command = new PutObjectCommand({
    Bucket: process.env.BUCKET,
    Key: key,
    ContentType: contentType,
    ACL: "private"
  });

  const presignedUrl = await getSignedUrl(s3, command, { expiresIn: 300 });

  res.json({ presignedUrl, key });
});

export default router;

This endpoint ensures:

  • Validation upstream, before URL generation (prevent oversized or wrong-type files).

  • Short expiration (5 min = 300s) to reduce window for attack.

  • Minimal permissions via IAM on the signer.

Step 2: Uploading Client-Side with Correct Headers

In React or plain JS:

async function uploadFile(file, signed) {
  const resp = await fetch(signed.presignedUrl, {
    method: 'PUT',
    headers: { 'Content-Type': file.type },
    body: file
  });
  if (!resp.ok) throw new Error(`Upload failed: ${resp.status}`);
  return signed.key;
}

Important notes:

  • Content-Type must match the one used to sign the URL, else AWS S3 rejects the request.

  • Use HTTPS to prevent interception.

  • Don’t set Content-Length manually—browser handles it reliably.

Step 3: Handling Key Failure Scenarios

3.1 CORS Misconfiguration

Without proper CORS rules, PUT will fail. Ensure your bucket has:

{
  "AllowedOrigins": ["https://yourdomain.com"],
  "AllowedMethods": ["PUT"],
  "AllowedHeaders": ["Content-Type"],
  "ExposeHeaders": []
}

Medium

3.2 URL Expired Before PUT

If token is generated and cached by your frontend (or a CDN) longer than expiry, users hit 403 errors. Best defense:

  • Use reactive token generation.

  • Ensure UI caches URL for less than TTL.

  • Use s3:signatureAge bucket policies to limit reuse window.
    AWS Documentation

3.3 Token Replay or Leak

Presigned URL is effectively a bearer token. Anyone with it can upload to your bucket for the duration — even if not authenticated. Reduce risk by:

  • Binding IAM role strictly to the specific key prefix.

  • Using granular bucket policies.

  • Logging usage and monitoring via CloudTrail or S3 access logs. reinforce.awsevents.com

Step 4: Optional: Multipart Upload for Large Files

Above ~5 MB, leverage multipart uploads:

import { CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand } from "@aws-sdk/client-s3";
  • Start upload → get UploadId

  • Generate presigned URLs per part using UploadPartCommand

  • Client PUTs each chunk

  • Finalize with CompleteMultipartUploadCommand

  • Validate checksum / parts order DEV Community

Multipart gives resiliency—partial retransmission and resume support.

Security Checklist Before Production

Risk Area

Mitigation Strategy

Leaked presigned URL

Expire quickly (≤5 min), bind prefix, limited IAM scope

Wrong Content-Type

Enforce exact match on sign and PUT

Cached by clients

Avoid caching responses that include URL longer than TTL

Unlimited reuse

Monitor via CloudTrail (x-amz-authType=QueryString) and revoke signer credentials if abuse suspected

CORS overly permissive

Restrict AllowedOrigins, method to only your domain and PUT

File type bypass

Pre-validate size/type server-side before URL issuance

Final Takeaway

Presigned URLs are powerful: they let you offload file traffic, enforce upload policies, and avoid server-side bandwidth costs. But they’re insecure if misconfigured or abused.

Key success factors:

  1. Validate user and file metadata server-side.

  2. Issue short-lived URLs, bound to specific S3 prefixes.

  3. Enforce strict CORS and content-type matching.

  4. Monitor usage and support URL invalidation via credential revocation.

  5. Use multipart upload for large files safely.

NEVER MISS A THING!

Subscribe and get freshly baked articles. Join the community!

Join the newsletter to receive the latest updates in your inbox.

Footer Background

About Cerebrix

Smarter Technology Journalism.

Explore the technology shaping tomorrow with Cerebrix — your trusted source for insightful, in-depth coverage of engineering, cloud, AI, and developer culture. We go beyond the headlines, delivering clear, authoritative analysis and feature reporting that helps you navigate an ever-evolving tech landscape.

From breaking innovations to industry-shifting trends, Cerebrix empowers you to stay ahead with accurate, relevant, and thought-provoking stories. Join us to discover the future of technology — one article at a time.

2025 © CEREBRIX. Design by FRANCK KENGNE.

Footer Background

About Cerebrix

Smarter Technology Journalism.

Explore the technology shaping tomorrow with Cerebrix — your trusted source for insightful, in-depth coverage of engineering, cloud, AI, and developer culture. We go beyond the headlines, delivering clear, authoritative analysis and feature reporting that helps you navigate an ever-evolving tech landscape.

From breaking innovations to industry-shifting trends, Cerebrix empowers you to stay ahead with accurate, relevant, and thought-provoking stories. Join us to discover the future of technology — one article at a time.

2025 © CEREBRIX. Design by FRANCK KENGNE.

Footer Background

About Cerebrix

Smarter Technology Journalism.

Explore the technology shaping tomorrow with Cerebrix — your trusted source for insightful, in-depth coverage of engineering, cloud, AI, and developer culture. We go beyond the headlines, delivering clear, authoritative analysis and feature reporting that helps you navigate an ever-evolving tech landscape.

From breaking innovations to industry-shifting trends, Cerebrix empowers you to stay ahead with accurate, relevant, and thought-provoking stories. Join us to discover the future of technology — one article at a time.

2025 © CEREBRIX. Design by FRANCK KENGNE.