For a mobile app that uploads photos to Amazon S3, you can hand the device temporary AWS credentials from a Cognito identity pool, or route uploads through your own API. How do you decide, and what must the identity pool's IAM role policy do if you hand out credentials?
answer
- who is in the request path
- IAM only knows principals and ARNs
- the policy variable is the whole game
- a wildcard resource is the classic bug
- authorize small, transfer direct
basics
~20 sDecide on how much you need to enforce per request. Identity pool credentials remove a hop and scale for free, but every rule must be expressible in an IAM policy — so the role must confine each user to their own key prefix using the identity's subject as a policy variable. Anything beyond that argues for your own API.
solid answer
~50 sHanding the device credentials means the device talks to S3 directly: no bandwidth through your compute, no upload timeout to manage, and it scales without you. The cost is that IAM becomes your only enforcement point, so the authenticated role's policy must be genuinely per-user — scoping `s3:PutObject` to a key prefix built from `${cognito-identity.amazonaws.com:sub}` rather than to `arn:aws:s3:::bucket/*`. A wildcard there means every signed-in user can read and overwrite every other user's objects, and it is the single most common way this pattern is broken. IAM also cannot express content rules: it will not check file type, enforce a per-day quota, scan for malware, or write a database row alongside the upload. When you need those, either route through your API or keep the direct upload but have your API mint a scoped, short-lived upload authorization per request. Also remember the credentials sit on a device you do not control for their full validity.
code
json · 11 lines{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "OwnPrefixOnly",
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::photos-bucket/private/${cognito-identity.amazonaws.com:sub}/*"
}
]
}go deeper
Know that an identity pool gives the device real AWS credentials, and that what those credentials can do is decided entirely by the IAM role policy behind them.
Be able to write the per-user scoping — an S3 prefix built from the Cognito identity policy variable — and say what breaks when the resource is a bucket-wide wildcard.
Weigh the direct path against the proxied one on bandwidth, enforcement and credential exposure, and propose the authorize-then-transfer-direct middle path with its tradeoffs.
Set the boundary for the whole product: which authorization rules are expressible in IAM and therefore may live there, which require code in the request path, and how you keep that line from blurring feature by feature.
## The two shapes **Credentials on the device.** The app signs in, exchanges its token at a Cognito identity pool, and receives temporary AWS credentials for the pool's authenticated role. The AWS SDK on the device then calls S3 directly. **Everything through your API.** The device calls your service; your service, running with its own execution role, performs the S3 write. The device never holds AWS credentials. They are not equivalent, and the choice is a genuine architecture decision rather than a preference. ## What direct credentials buy Upload bytes never traverse your compute. That removes a real cost and a real operational problem — large uploads over slow mobile networks tie up connections, blow request timeouts, and make your service's capacity a function of your users' bandwidth. The direct path also gets S3's multipart upload, resumability and transfer acceleration for free, and it scales without any work from you. ## What direct credentials cost **IAM becomes the entire authorization surface.** Whatever your rule is, it must be expressible as an IAM policy evaluated by S3, because there is no code of yours in the path. The essential technique is the identity policy variable: the authenticated role's policy interpolates the caller's Cognito identity into the resource ARN, confining each user to their own prefix. ```json { "Effect": "Allow", "Action": ["s3:PutObject", "s3:GetObject"], "Resource": "arn:aws:s3:::photos-bucket/private/${cognito-identity.amazonaws.com:sub}/*" } ``` Without that interpolation — with `photos-bucket/*` — every authenticated user of the pool can read and overwrite every other user's data with credentials the app hands them. This is the classic finding in a review of this pattern, and it is worth stating unprompted. The role's **trust policy** matters just as much: it must trust `cognito-identity.amazonaws.com` and condition on your identity pool ID via `cognito-identity.amazonaws.com:aud`, and typically on `cognito-identity.amazonaws.com:amr` to distinguish authenticated callers from guests. A pool with an unauthenticated role attached is handing AWS credentials to anyone at all, which is fine for a public read path and catastrophic for a writable one. **IAM cannot express business rules.** It will not enforce a per-user storage quota, reject a 400 MB file, validate that the object is actually an image, scan it, resize it, or write the accompanying row in your database. Every one of those requires code. You can bolt some of it on afterwards with an S3 event notification, but that is *after* the object exists and after you have paid to store it. **The credentials live on a device you do not control.** They are short-lived, but for their validity they are real AWS credentials on a possibly rooted phone, extractable by anyone with the device. Their blast radius is exactly the role policy, which is another reason the policy must be tight rather than convenient. ## The middle path Most mature systems do not pick a pure side. The device calls your API to *authorize* an upload — and your API, having applied the quota check, the content-type rule and the database write, returns a narrowly scoped, short-lived permission for exactly one object. The bytes then flow directly to S3, so you keep the bandwidth win, and your code stays in the decision path, so you keep enforcement. The cost is a round trip per upload and a service that must stay available, but that request is small and cheap, which is a very different thing from proxying the payload. ## How to decide, briefly - Are all the rules expressible as "this principal may act on this ARN"? If yes, direct credentials are clean and cheap. - Do you need per-request business logic, quotas, validation, or a transactional side effect? Then your code must be in the path — via an authorizing call, if not via a proxy. - Is any part of this writable by unauthenticated callers? Then be extremely deliberate about the unauthenticated role, or do not have one. - Would you be comfortable publishing the role's policy? If a leaked credential for that role would be a serious incident, the policy is too broad regardless of which shape you pick. And the question underneath all of it: how much of your product's authorization model are you willing to encode in IAM? IAM is excellent at resource-shaped rules and cannot express anything else. Teams that answer this early keep a coherent boundary; teams that discover it at the third feature end up with half the rules in policy documents and half in application code, and no one able to say what a given user can actually do.
- What is the failure mode if the authenticated role's resource is written as arn:aws:s3:::photos-bucket/*?Every signed-in user of that identity pool gets credentials that can read, overwrite and delete every other user's objects in the bucket. It is not a bug that surfaces in testing, because each user's own uploads work perfectly. It surfaces as a data breach, and the only thing standing between users was supposed to be that resource ARN.
- You need a per-user daily upload quota. Can the identity pool role enforce it?No. IAM evaluates a single request against principal, action, resource and conditions; it has no notion of accumulated state across requests. A quota requires something that counts, which means your code. Either authorize each upload through your API, which checks and increments the counter, or account after the fact from S3 events and accept that enforcement lags.
- Does an unauthenticated role on the identity pool change your risk calculus?Substantially. An unauthenticated role hands temporary AWS credentials to anyone who asks, with no sign-in at all, so its policy is effectively public permission. It is reasonable for anonymous reads of public content and rarely reasonable for writes. If you do not need guest access, do not configure the role, and condition the authenticated role's trust policy on the amr claim so the two never blur.
saying these in an interview costs you the question
- Grants the authenticated role bucket-wide access
- Thinks IAM can enforce file size or content type
- Treats credentials on a device as safe because they expire
- Leaves an unauthenticated role writable for convenience
- Proxies large uploads through the API without considering the cost