A presigned URL is not ongoing authorization
A presigned S3 (or GCS, or Azure SAS) URL feels like an access check, because the storage service rejects anyone without a valid signature. But the signature only proves one thing: at the moment you minted it, your app d
A presigned S3 (or GCS, or Azure SAS) URL feels like an access check, because the storage service rejects anyone without a valid signature. But the signature only proves one thing: at the moment you minted it, your app decided this caller could read this object.
After that, the URL is a bearer token. Anyone holding it can use it until it expires, even if:
- the user was removed from the workspace five minutes later
- the document was moved to a restricted folder
- the link was pasted into a ticket, a chat, or a log line
A small example
# Minting is the authorization moment
def get_download_url(user, doc_id):
doc = docs.get(doc_id)
if not authorize(user, "read", doc):
raise Forbidden()
return s3.generate_presigned_url(
"get_object",
Params={"Bucket": BUCKET, "Key": doc.key},
ExpiresIn=60, # keep this short
)
The check happens in authorize(user, "read", doc), not in S3. S3 only verifies your signature.
What helps
- Authorize at mint time, every time. Never cache "this user can download" and hand out fresh URLs from the cache.
- Keep expiry short. Seconds to a few minutes for downloads. Hours-long URLs are standing grants.
- Don't treat the URL as the permission. If a page re-renders, mint a new URL after a new check instead of reusing an old one.
- Keep URLs out of logs and analytics. Query strings travel further than you expect.
- For revocation-sensitive data, proxy instead. Stream through your app so every request gets a fresh decision.
The takeaway
A presigned URL is the output of an authorization decision, not a replacement for one. Make the decision in your app, make it per object, and keep the output short-lived.
How do you handle revocation for long-lived download links in your apps?
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.