AWS Identity and Access Management decides who and what can call which AWS API. How users, roles and policies fit together, how policy evaluation works, and how backend services get credentials without access keys.
AWS Identity and Access Management (IAM) is the service that decides whether a request to an AWS API is allowed. Every call, from a developer running aws s3 ls to a Fargate task reading a secret, is signed with credentials that belong to an IAM identity, and IAM checks that identity's policies before the request reaches S3 or Secrets Manager.
Most IAM trouble in backend teams comes from two places: long-lived access keys that end up in .env files, CI variables or an AI agent's context, and policies that were widened to "*" to get a deploy unstuck. This guide covers how IAM works, the parts that matter for backend services, and how Encore (infrastructure from code) handles roles for the services it deploys.
An IAM policy is a JSON document of statements, each with an Effect (Allow or Deny), a list of Action values such as s3:GetObject, and the Resource ARNs they apply to, optionally narrowed by Condition keys. A policy that lets a service read objects under one prefix looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::acme-receipts/receipts/*"
}
]
}
AWS evaluates every applicable policy together, and the evaluation logic has three rules worth memorizing: everything is denied by default, an explicit Allow overrides that default, and an explicit Deny anywhere overrides any Allow. Service control policies in AWS Organizations, permission boundaries and session policies can only narrow what identity policies grant, which is why a request can fail even though the role's own policy allows it.
An IAM user has permanent credentials. Access keys for a user stay valid until someone rotates or deletes them, so a key copied into a laptop, a CI secret or a chat transcript keeps working long after the person who needed it has moved on. AWS's own IAM best practices recommend federation with temporary credentials for people and IAM roles for workloads.
A role has a trust policy that says who may assume it, such as the ECS tasks service, an EC2 instance profile, a GitHub Actions OIDC provider or another AWS account, and permission policies that say what it can do. Assuming the role returns temporary credentials from STS, and AWS SDKs refresh them on their own. On ECS, the task role is what application code uses, while the separate task execution role lets ECS pull the image and read secrets at startup.
A backend with a few services usually needs one role per service, each allowed to touch only the queues, buckets, secrets and databases that service uses. Done by hand, that means writing a role, a trust policy and a permission policy for every service in Terraform or CDK, keeping resource ARNs in sync as resources are renamed, and resisting the temptation to give everything one shared role with broad permissions. The policies drift from the code because nothing connects them: a new s3:PutObject call in the application fails at runtime in staging until someone edits the policy.
Encore is infrastructure from code for TypeScript and Go, with a platform that provisions and deploys into your own AWS or GCP account. You declare infrastructure such as buckets, Pub/Sub topics and databases as objects in your code, and when you deploy, Encore provisions an IAM role for the service along with the resources themselves, as described in the AWS infrastructure docs.

Run in your terminal to get started locally.
The receipts service below stores files in a private bucket and hands out short-lived download links. There is no policy JSON, ARN or access key in the code:
// receipts/encore.service.ts
import { Service } from "encore.dev/service";
export default new Service("receipts");
// receipts/receipts.ts
import { api } from "encore.dev/api";
import { Bucket } from "encore.dev/storage/objects";
// An S3 bucket on AWS, a local bucket under encore run.
export const receipts = new Bucket("receipts", { versioned: false });
export const upload = api(
{ expose: true, method: "POST", path: "/receipts/:id" },
async ({ id, body }: { id: string; body: string }): Promise<{ size: number }> => {
const attrs = await receipts.upload(`receipts/${id}.txt`, Buffer.from(body), {
contentType: "text/plain",
});
return { size: attrs.size };
},
);
export const link = api(
{ expose: true, method: "GET", path: "/receipts/:id/link" },
async ({ id }: { id: string }): Promise<{ url: string }> => {
const { url } = await receipts.signedDownloadUrl(`receipts/${id}.txt`, { ttl: 900 });
return { url };
},
);
encore run starts the service with a local bucket, so the upload and the signed link work on a laptop without AWS credentials. On deploy, Encore creates the S3 bucket and provisions an IAM role for the service, and the code reaches the bucket through that role's temporary credentials, so there are no access keys in the repo, the environment or the CI system. Object storage usage is covered in the TypeScript object storage docs, and the same model applies in Go.
An AI coding agent can add a bucket or a topic to the code and test it under encore run, and the access it adds shows up in a reviewable code diff instead of a hand-edited policy. Secrets that the code does need are set with encore secret set and stored in AWS Secrets Manager, as covered in the secrets docs.
Best fit: Teams who want each backend service on AWS to run under its own IAM role, derived from the resources its code uses.
AWS IAM controls authentication and authorization for AWS. It defines identities such as users and roles, and policies that say which actions those identities can take on which resources, for example reading objects from one S3 bucket or sending messages to one SQS queue.
An IAM user is a long-lived identity with permanent credentials such as a password or access keys. An IAM role has no permanent credentials. It is assumed by a person, a service or a workload, which receives temporary credentials from AWS STS that expire after a set time.
Yes. AWS does not charge for IAM users, groups, roles or policies. You pay for the AWS resources those identities use.
The service runs with an IAM role, such as an ECS task role, whose policy allows the S3 actions it needs on that bucket. The AWS SDK picks up temporary credentials for the role automatically. With Encore, you declare the bucket in code with new Bucket() and call upload() or signedDownloadUrl() from the service, and Encore provisions an IAM role for the service when it deploys to your AWS account.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.