Valet key pattern
How pre-signed URLs and scoped credentials give clients direct, time-limited access to resources without routing data through your servers. Covers S3 pre-signed URLs, SAS tokens, scoping, and upload security.
TL;DR
- The valet key pattern gives clients direct, time-limited access to a resource without routing data through your application server.
- Your server generates a pre-signed URL or scoped token, hands it to the client, and steps aside.
- The client uploads or downloads directly to/from the storage service. Your server never handles the file bytes.
- Pre-signed URLs embed a cryptographic signature scoped to one operation, one exact object key, and one expiry time. They cannot be reused or extended.
- The pattern eliminates server-side bandwidth costs, removes a scaling bottleneck, and is the standard approach for direct S3 uploads in modern web and mobile apps.
The Problem With Proxying Uploads and Downloads
The naive design for file uploads: client sends the file to your API server, your server validates it, your server stores it in S3. For downloads, your server fetches from S3 and streams it back. Simple to implement, obvious to reason about.
But every byte passes through your application server twice. A 50 MB video upload consumes 50 MB of inbound bandwidth on your server and another 50 MB outbound from your server to S3. Downloads add the same again in reverse. Your server is paying a full bandwidth tax on every transfer, for work it is not actually doing.
At scale this becomes expensive and fragile. A hundred concurrent 50 MB uploads push 5 GB per minute through your application tier. File transfers are I/O-bound, not CPU-bound, so you cannot simply scale CPU. Your application servers saturate their network interfaces while sitting idle on compute.
The server is not adding value here. It is a very expensive pipe.
One-Line Definition
The valet key pattern issues a short-lived, narrowly-scoped credential that lets a client communicate directly with a backend resource, removing the application server from the data path entirely.
Analogy
A hotel valet takes your car key, drives to the parking garage, and returns a valet stub. That stub lets you retrieve exactly one car (yours), from exactly one garage, within a limited window. The valet cannot drive your car to another city. You cannot use the stub to access someone else's car.
Your application server is the valet. The pre-signed URL is the stub. The client gets exactly one operation, one object, a short window. When the window closes, the credential is worthless.
Solution
Instead of being the data proxy, your server generates a pre-signed URL that embeds time-limited credentials. The client uses that URL to upload or download directly. Your server handles two small JSON API calls; the heavy data transfer happens entirely between the client and storage.
The sequence has three distinct phases. First, your server issues the credential. Second, the client does the transfer entirely without your server. Third, your server records the completion. That middle phase is the entire point.
How a Pre-Signed URL Works
A pre-signed URL is a regular HTTPS URL with authentication embedded in its query parameters. There is no separate credential request at S3 when the client uses it. S3 validates the embedded signature directly.
AWS signs the URL using your IAM credentials (access key + secret key). The signature covers the HTTP method, the exact S3 bucket and object key, an expiry timestamp, and optionally Content-Type and Content-Length. When S3 receives the request, it re-computes the signature from the request components and compares it to the embedded one. If anything was tampered with, the signatures don't match and S3 returns a 403.
import boto3
def generate_upload_url(user_id: str, filename: str, content_type: str, file_size: int) -> dict:
s3 = boto3.client('s3', region_name='us-east-1')
object_key = f"users/{user_id}/uploads/{filename}"
url = s3.generate_presigned_url(
ClientMethod='put_object',
Params={
'Bucket': 'my-user-uploads',
'Key': object_key,
'ContentType': content_type,
'ContentLength': file_size,
},
ExpiresIn=300 # 5 minutes
)
return {'upload_url': url, 'object_key': object_key, 'expires_in': 300}
def generate_download_url(object_key: str, expiry_seconds: int = 3600) -> str:
s3 = boto3.client('s3', region_name='us-east-1')
return s3.generate_presigned_url(
ClientMethod='get_object',
Params={'Bucket': 'my-user-uploads', 'Key': object_key},
ExpiresIn=expiry_seconds
)
The client uses this URL with a standard HTTP PUT. No AWS SDK needed.
PUT https://my-user-uploads.s3.amazonaws.com/users/123/uploads/avatar.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA...&X-Amz-Date=20260405T...&X-Amz-Expires=300&X-Amz-Signature=abc...
Content-Type: image/jpeg
Content-Length: 1048576
[binary bytes]
The credentials are in the URL, not in a header. You can send this URL to any client (mobile app, browser, third-party system) and they can execute the upload without ever knowing your AWS credentials.
Scoping and Security
The narrower the scope of the credential, the safer the pattern is. I've seen teams generate pre-signed URLs with 24-hour TTLs that could be used for any object in a bucket. That defeats the security model entirely.
Scope by operation. Generate separate URLs for PUT (upload) and GET (download). Never use the same pre-signed URL for both directions.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.