FileDummy Logo
FileDummy
File Security & Integrity

Presigned URLs: Secure File Downloads from S3 and Cloudflare R2

Generate time-limited presigned URLs for secure file downloads from AWS S3 and Cloudflare R2 using the AWS SDK v3 in Node.js. No public bucket exposure required.

November 1, 20269 min read1,420 views
nodejsaws-s3cloudflare-r2presigned-urlsecurity

Allowing users to download private assets directly from Amazon S3 or Cloudflare R2 without routing massive data streams through your application servers drastically reduces server load and bandwidth costs. Presigned URLs provide secure, time-limited download links signed by your cloud credentials.

Architecture: Why Direct Presigned Downloads?

Code
[Browser] -- 1. GET /api/download-link?file=report.pdf --> [Node.js API Server]
                                                            | Generates Presigned URL
[Browser] <-- 2. Returns signed URL (expires in 15m) <-----+ (Zero bandwidth used!)

[Browser] -- 3. GET https://bucket.r2.cloudflarestorage.com/report.pdf?X-Amz-Signature=... --> [Cloudflare R2]
[Browser] <-- 4. Direct high-speed download at line rate ---------------------------------------+
Verified Test Asset.pdf

Download our 100MB PDF sample to benchmark time-to-first-byte (TTFB) with presigned download URLs.

Download 100MB PDF Sample →

Generating Presigned GET URLs in Node.js (AWS SDK v3)

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

const s3 = new S3Client({
  region: 'auto',
  endpoint: process.env.R2_ENDPOINT,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
});

export async function createDownloadUrl(
  bucketName: string,
  objectKey: string,
  fileName: string,
  expiresInSeconds = 900 // 15 minutes
): Promise<string> {
  const command = new GetObjectCommand({
    Bucket: bucketName,
    Key: objectKey,
    // Force browser to download with proper filename instead of opening in tab
    ResponseContentDisposition: `attachment; filename="${encodeURIComponent(fileName)}"`,
  });

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

Security Best Practices

  • Short expiration windows: Limit expiration to 5–15 minutes. Once the browser initiates the download stream, connection continuity is preserved even if the URL expires mid-transfer.
  • Authorization checks first: Always verify user session permissions and rate limits in your API endpoint before generating and returning the signed URL.
NDL

Nguyen Dai Long

Author

Backend Lead • Distributed Systems & Cloud Edge Architecture Specialist

4+ years designing high-throughput file ingestion pipelines, database architectures, and distributed edge storage on Cloudflare R2 & AWS S3. Founder of FileDummy and the NDL Ecosystem.

Was this article helpful?

Click Like to support the author and help other developers discover this guide.

Engineering Discussion & Feedback (0)

Share benchmark results, report edge cases, or ask technical questions.

0/3000

No comments yet. Be the first developer to start the discussion!

Frequently Asked Questions

How long should a presigned URL be valid?â–¾

Match expiry to the use case. For direct download links in a UI: 15–60 minutes. For email links: 24–72 hours. For machine-to-machine: 5–15 minutes. A leaked URL grants temporary access, so keep expiry as short as practical.

Can presigned URLs be revoked before they expire?â–¾

You cannot directly revoke a presigned URL. To effectively revoke, move or delete the object in S3/R2. Use short expiry times for fine-grained access control.

What is the difference between a presigned URL and a public bucket URL?â–¾

A public URL is permanently accessible to anyone. A presigned URL is time-limited, tied to specific IAM permissions, and optionally IP-restricted. Use presigned URLs for private files.

Related Articles