Signed URLs for Secure Access
Making objects public is simple but often wrong. User documents, private uploads, and sensitive files shouldn't be accessible to everyone. Signed URLs solve this elegantly — they provide temporary, controlled access to private objects without exposing your storage credentials.
Why Signed URLs Matter
The traditional approach to serving private files involves your server downloading from storage, then sending to the user. This wastes bandwidth and server resources. With signed URLs, users download directly from storage, but only with your permission.
Here's the flow: a user requests a file, your server verifies they're authorized, generates a signed URL, and returns it. The user's browser then downloads directly from storage. The URL works only for a limited time and only for that specific object.
Generating Download URLs
Creating a signed download URL is straightforward:
url = s3.generate_presigned_url('get_object',
Params={
'Bucket': 'my-bucket',
'Key': f'user/{user_id}/document.pdf'
},
ExpiresIn=300 # 5 minutes
)
# Return this URL to the client
The ExpiresIn parameter controls how long the URL remains valid. Shorter times are more secure — five minutes is usually plenty for a download to start.
Enabling Direct Uploads
Signed URLs also enable direct uploads from browsers, bypassing your server entirely. This dramatically reduces server load for file uploads:
from uuid import uuid4
url = s3.generate_presigned_url('put_object',
Params={
'Bucket': 'my-bucket',
'Key': f'uploads/{uuid4()}.jpg',
'ContentType': 'image/jpeg'
},
ExpiresIn=300
)
Your frontend receives this URL and uploads directly:
await fetch(signedUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': 'image/jpeg' }
});
The upload goes straight to storage. Your server never handles the file data — just the authorization decision.
Security Best Practices
Keep expiration times short. Five to fifteen minutes handles most use cases. Longer times increase the window for URL leakage.
Validate before generating. Always verify the user has permission to access or upload the specific file. Don't generate URLs blindly based on user input.
Avoid logging signed URLs. They contain sensitive signatures. If URLs appear in logs, anyone with log access gains file access.
Use unique keys for uploads. Generate random filenames (like UUIDs) to prevent users from overwriting each other's files.