The Scaling Problem
You have one million users. Each user should only be able to read and write their own data. You cannot create one million IAM roles — IAM has a hard limit of 5,000 roles per account, and even if it didn't, managing them would be operationally impossible.
The solution is a single IAM role whose policy contains variables that resolve to the current user's identity at the moment the credentials are issued. AWS evaluates these variables when checking each API call, so the same policy document enforces different boundaries for every user.
The Policy Variable
The key variable is ${cognito-identity.amazonaws.com:sub}, which resolves to the user's Cognito Identity ID — a stable, unique identifier like us-east-1:a1b2c3d4-e5f6-7890-abcd-ef1234567890. This ID is consistent across sessions for the same user.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPersonalS3Folder",
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": [
"arn:aws:s3:::my-app-bucket/users/${cognito-identity.amazonaws.com:sub}/*"
]
},
{
"Sid": "AllowPersonalDynamoDBRow",
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem"],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/UserProfiles",
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]
}
}
}
]
}
What This Achieves
When Alice's credentials are issued, AWS substitutes her Identity ID into the policy at evaluation time. Her s3:PutObject call to /users/alice-id/photo.jpg succeeds. Her attempt to call s3:PutObject on /users/bob-id/photo.jpg fails with AccessDenied — not because of application logic, but because IAM itself enforces the boundary. Even if your application has a bug that constructs the wrong path, AWS rejects the call.
The DynamoDB condition dynamodb:LeadingKeys enforces the same pattern at the database row level. Alice can only GetItem or PutItem on rows where the partition key equals her Identity ID. This is true row-level security enforced by the IAM control plane, not your application code.
A common interview question: "How do you implement per-user data isolation in DynamoDB for a mobile app without a backend?" The answer is Cognito Identity Pools + IAM policy variables + the dynamodb:LeadingKeys condition key. This combination enforces isolation at the AWS API layer, meaning no amount of client-side tampering can access another user's data. Follow-up: "What's the partition key design?" — each user's records use their Cognito Identity ID as the partition key.