KAHIBARO
Discord Login Register

5.9.7. Presigned URLs

Why Presigned URLs Matter

When you work with file storage services like Amazon S3 or other S3 compatible storage, you often face a problem:

Presigned URLs solve this by letting your backend say:

“Here is a special URL that lets you upload or download exactly this file, in this way, for a short time. After that it stops working.”

You keep full control in your backend, but the browser talks directly to the storage service.


What Is a Presigned URL?

A presigned URL is a normal HTTP URL with a hidden “signature” added as query parameters.

The signature is created by your backend using your secret access keys. The storage service can verify this signature.

So the URL both:

A presigned URL usually specifies:

Key rule: A presigned URL is temporary authorization for a specific operation on a specific object. After it expires, it must not work anymore.

Example of a presigned URL (shortened):

text
https://my-bucket.s3.amazonaws.com/user-123/avatar.png
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=AKIA...
&X-Amz-Date=20260828T120000Z
&X-Amz-Expires=300
&X-Amz-Signature=abcd1234...
&X-Amz-SignedHeaders=host

Clients just use it as a normal URL, for example in a browser or curl.


Download vs Upload Presigned URLs

You can use presigned URLs for:

PurposeHTTP methodWho calls the URLData direction
Download fileGETBrowser or mobile clientStorage → Client
Upload filePUT or POST formBrowser or mobile clientClient → Storage

The difference is important:

You usually:

  1. Ask the backend for a presigned URL.
  2. Backend returns JSON with that URL (and sometimes additional form fields).
  3. Client uses the URL directly against the storage server.

Example: Download flow

  1. Client: GET /files/123/download-link
  2. Backend: checks permissions for file 123 and user.
  3. Backend: generates presigned GET URL for S3.
  4. Backend: returns JSON:
json
   {
     "url": "https://my-bucket.s3.amazonaws.com/...signed..."
   }
  1. Client: redirects browser to that URL or uses it with fetch.

Example: Upload flow

  1. Client: POST /files/upload-link with JSON specifying file name, type, etc.
  2. Backend: validates user and file info.
  3. Backend: generates presigned PUT URL.
  4. Backend: returns JSON with the URL.
  5. Client: uploads file directly to storage with PUT:
bash
   curl -X PUT -T myfile.png "https://my-bucket.s3.amazonaws.com/...signed..."

Security Properties and Best Practices

Presigned URLs are powerful, but you must use them carefully.

Expiration Time

The URL has an expiration, usually in seconds. For example, 300 seconds is 5 minutes.

Rule: Use the shortest expiration that still works for your use case.
Never create presigned URLs that last hours or days unless you absolutely need that behavior.

Common values:

Use caseTypical expiration
File upload from browser5 to 15 minutes
One-time secure download5 to 60 minutes
Public-ish download (low risk)1 to 24 hours

Longer expiration means:

Least Privilege per URL

Each presigned URL must be scoped as tightly as possible:

Rule: A presigned URL must grant permission to one specific file operation, not to a folder or pattern.

Bad example:

Good example:

Never Expose Your Secret Keys

Clients must never see:

All signing happens in your backend only. The client sees only the result, the presigned URL.


Presigned URLs and Backend Responsibility

It might look like presigned URLs bypass your backend, but your backend is still where all decisions are made.

Your backend must answer these questions before generating a URL:

Example logic in plain steps:

  1. User calls POST /users/me/avatar/upload-url.
  2. Backend checks:
    • User is logged in.
    • File type and size requested are acceptable.
  3. Backend decides on the final storage path, for example users/{user_id}/avatar.png.
  4. Backend generates a presigned PUT URL for that path with a short expiration.
  5. Backend returns the URL to the client.
  6. Client uploads directly to storage.
  7. Optionally, backend later confirms that the upload succeeded, for example with a callback or a separate “mark as uploaded” endpoint.

Example Flow with S3 Compatible Storage

To make the idea concrete, here is a simple pattern using a generic S3 compatible storage.

1. Backend generates upload URL

Request:

http
POST /files/upload-url
Content-Type: application/json
{
  "file_name": "photo.png",
  "content_type": "image/png"
}

Backend steps:

  1. Read user ID from authentication.
  2. Create a filepath like users/{user_id}/photos/{uuid}.png.
  3. Decide expiration, for example 600 seconds.
  4. Ask the storage SDK to generate a presigned PUT URL for that key.
  5. Return JSON:
json
{
  "upload_url": "https://my-bucket.s3.amazonaws.com/users/123/photos/abcd-uuid.png?...signature...",
  "file_path": "users/123/photos/abcd-uuid.png"
}

2. Client uploads directly

Client uses any HTTP tool:

bash
curl -X PUT \
  -H "Content-Type: image/png" \
  --upload-file ./photo.png \
  "https://my-bucket.s3.amazonaws.com/users/123/photos/abcd-uuid.png?...signature..."

Storage validates:

If OK, storage saves the object.

3. Backend uses the file path

Backend stores file_path in the database, related to the user or another entity. The database only contains paths like:

text
users/123/photos/abcd-uuid.png

Whenever a download link is needed, the backend:

  1. Checks permissions.
  2. Uses the stored path.
  3. Generates a new presigned GET URL for that path.
  4. Returns it to the client.

Common Pitfalls and How to Avoid Them

Pitfall 1: Too Long Expiration

If you use something like 1 day for all URLs, a leaked link is dangerous for much longer.

Avoid by:

Pitfall 2: Directly Using User-provided Paths

If a client can choose the storage key freely, they may overwrite other users’ files or important objects.

Avoid by:

Pitfall 3: Not Checking Permissions

If you generate presigned URLs just because a user knows a file ID, they could guess another user’s file ID and get access.

Avoid by:

Pitfall 4: Mixing Public and Private Access

Sometimes a bucket is public, and you still generate presigned URLs. This reduces the benefit of access control.

Choose a clear strategy:

When to Use Presigned URLs vs Backend Proxy

There are two main patterns for serving or receiving files.

PatternHow it worksProsCons
Presigned URLClient ↔ Storage directly, URL signed by backendScalable, offloads trafficSlightly more complex client logic
Backend proxy (no presign)Client ↔ Backend ↔ Storage. Backend streams fileSimpler client API, more controlBackend handles all file traffic

Presigned URLs are typically best when:

Backend proxy is sometimes better when:

In many real systems you use both patterns, depending on the endpoint.


Summary

Views: 6

Comments

Please login to add a comment.

Don't have an account? Register now!