What is an Amazon S3 presigned URL, and whose permissions does the person holding one actually exercise?
answer
- no AWS credentials reach the client
- signature and expiry live in the query string
- one method, one bucket, one key
- the signer's permissions, checked at request time
- a bearer token with a countdown
basics
~20 sAn S3 presigned URL is an object URL carrying a SigV4 signature and an expiry in its query string. Whoever holds it performs one specific operation on one specific object using the signing principal's permissions, with no AWS credentials of their own.
solid answer
~40 sA presigned URL is a normal S3 object URL with SigV4 authentication moved into the query string — `X-Amz-Credential`, `X-Amz-Date`, `X-Amz-Expires`, `X-Amz-SignedHeaders` and `X-Amz-Signature`. I generate it server-side using credentials that already hold `s3:GetObject` or `s3:PutObject` on that key, then hand the URL to a browser or partner that has no AWS identity at all. S3 verifies the signature, checks the expiry, and evaluates the request as though my signing principal had made it, so the holder can never do more than the signer could. The signature covers the HTTP method, bucket, key and any signed headers, so one URL is good for exactly one operation on exactly one object. That is how a multi-gigabyte upload goes straight from the browser to S3 instead of streaming through my API servers.
go deeper
Be able to say plainly that a presigned URL lets a browser read or write one object directly, that the signature and expiry ride in the query string, and that no AWS credentials go to the client.
Explain that the signature covers method, bucket, key and signed headers, and that S3 evaluates the request against the signing principal at request time — so removing the signer's permission invalidates outstanding URLs.
Show the production discipline: the server derives the key, expiries are minutes not days, the signing role is scoped to one prefix, and you treat the URL as a leakable bearer token in logs and referrers.
Own the tradeoff between direct-to-S3 transfer and proxying through your own tier — bandwidth cost and latency against the loss of a single inspection point for validation, virus scanning, quotas and audit.
## The problem it solves A browser or a partner system needs to read or write one object in a private S3 bucket. The bad answers are to make the bucket public, to ship AWS access keys to the client, or to proxy every byte through your own API. Proxying costs you bandwidth, request latency and memory, and it turns a 2 GB upload into a 2 GB problem for your application servers. A presigned URL removes all three problems: the client talks to S3 directly, and the only thing you gave away is a time-boxed permission to do one thing. ## What the URL actually contains Signature Version 4 normally authenticates a request through an `Authorization` header. Presigning moves the same material into the query string so a plain browser navigation or `fetch` can carry it: ``` https://my-bucket.s3.eu-west-1.amazonaws.com/reports/2026-q1.pdf ?X-Amz-Algorithm=AWS4-HMAC-SHA256 &X-Amz-Credential=AKIA.../20260321/eu-west-1/s3/aws4_request &X-Amz-Date=20260321T101500Z &X-Amz-Expires=900 &X-Amz-SignedHeaders=host &X-Amz-Signature=9f86d0... ``` The signature is an HMAC over a canonical form of the request: the HTTP method, the path (bucket and key), the canonical query string, the list of signed headers, and a payload hash — which for presigned URLs is the literal `UNSIGNED-PAYLOAD`, because the signer does not have the body. Change any signed element and the signature no longer matches; S3 answers `403` with `SignatureDoesNotMatch`. Two consequences fall straight out of that. First, the URL is scoped to one method and one key: a URL presigned for `GetObject` on `reports/2026-q1.pdf` cannot fetch a different key, cannot list the bucket, and cannot be turned into a PUT. Second, if you sign a header — `Content-Type`, say — the client must send exactly that header value or the request fails. ## Where the permissions come from This is the part interviewers press on. A presigned URL grants nothing by itself. When the request arrives, S3 identifies the signing principal from `X-Amz-Credential`, verifies the signature and expiry, and then runs the normal authorization evaluation **as if that principal had made the request directly**: identity policy, bucket policy, any relevant Organizations or endpoint policy. So: - If the signing role cannot `s3:GetObject` that key, the presigned URL fails too. You cannot presign your way past a permission you do not have. - If permissions are removed after signing, the URL stops working — evaluation happens at request time, not at signing time. - An explicit `Deny` in the bucket policy still wins. Block Public Access does not interfere, because a presigned request is an authenticated request, not an anonymous public one. - Conversely, a badly scoped signer is dangerous: if you sign with an admin role and a bug lets a caller choose the key, you have handed out admin-shaped reads. Because the credentials are the signer's, the URL is a **bearer token**. Anyone who obtains it — from a log line, a chat message, a browser history entry — can replay it until it expires. Treat it as a secret with a countdown. ## Generating one Every SDK exposes it, and the operation is purely local: no API call to S3 is made when you presign, which is why it is fast and works offline. ```python url = s3.generate_presigned_url( "put_object", Params={"Bucket": "my-bucket", "Key": "uploads/abc123.png"}, ExpiresIn=900, ) ``` The CLI's `aws s3 presign s3://my-bucket/reports/2026-q1.pdf --expires-in 900` produces a GET URL; other methods come from the SDKs. ## The interview shape The canonical question is "how does a user upload a large file without it passing through your API?" The expected answer: the client asks your API for a presigned PUT URL; your API authenticates and authorizes *the user*, decides the object key itself (never letting the client dictate it), signs a short-lived URL, and returns it; the browser PUTs the bytes straight to S3. Your API sees a few hundred bytes of JSON instead of gigabytes of body. The same shape in reverse — a short-lived presigned GET — serves private downloads without making the bucket readable. What a presigned URL is *not*: it is not a way to grant a client ongoing access, not a substitute for authenticating the user in your own API, and not something you can hand out with a year-long expiry and forget about.
- If the signing role loses s3:GetObject an hour after you hand out a URL that is valid for six hours, what happens?The URL stops working immediately. Authorization is evaluated when the request arrives, not when the URL is signed: S3 verifies the signature and expiry, then runs the normal policy evaluation against the signing principal. Losing the permission — or an explicit deny appearing in the bucket policy — kills every outstanding URL signed by that principal for that key.
- Your API lets the caller supply the object key it wants presigned. Why is that a problem?You have turned your signing role's permissions into an open API. A caller can ask for a key belonging to another tenant and get a valid URL, because the signature is produced from whatever key you passed in. The server must derive the key itself from the authenticated user — a tenant prefix plus a generated identifier — and never echo back a client-chosen path.
- Does generating a presigned URL call S3?No. Presigning is a local cryptographic operation: the SDK builds the canonical request and HMACs it with the credentials it already holds. Nothing is registered server-side, which is why signing is instant, works without network access, and also why there is no list of issued URLs to revoke later.
saying these in an interview costs you the question
- Says the URL makes the object public to everyone forever
- Thinks the client needs its own IAM user or keys
- Claims a presigned URL bypasses bucket policies and explicit denies
- Believes one URL can be reused for other keys in the bucket
- Assumes authorization is checked at signing time, not request time