Updated Nov 5, 20266 min read

What Is AWS IAM? Users, Roles, Policies and How Backends Get Access

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.

AWS IAM at a glance

ConceptWhat it isTypical use in a backend team
IAM userLong-lived identity with a password or access keysLegacy CI or scripts. Avoid for workloads
IAM groupCollection of users sharing policiesGrouping the few remaining IAM users
IAM roleIdentity without permanent credentials, assumed for a sessionServices, CI with OIDC, cross-account access
Identity-based policyJSON document attached to a user, group or role"This role may read bucket X"
Resource-based policyJSON document attached to a resourceBucket policies, SQS queue policies, KMS key policies
STSSecurity Token Service that issues temporary credentialsEvery role assumption
IAM Identity CenterSingle sign-on for people across accountsDeveloper and admin console access

How IAM decides on a request

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.

Users vs roles

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.

IAM for backend services in practice

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.

How Encore handles IAM roles

Encore

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.

The Infrastructure page in the Encore dashboard with a database's configuration panel open, showing fields for vCPUs, memory, storage, availability type, max connections and backups.
Each provisioned resource can be reviewed and its configuration edited per environment in the Encore dashboard. The screenshot shows a GCP Cloud SQL database.

Set up the app

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

~
$ encore app create receipts --example=ts/empty$ cd receipts && mkdir receipts

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.

Good to know

  • IAM is global to an account, while most resources are regional, so policies reference regional ARNs.
  • AWS has no charge for IAM itself, and the IAM user guide is the reference for every policy element.
  • IAM Access Analyzer can generate a policy from CloudTrail activity, which helps trim roles that were created too broad.

Frequently asked questions

What is AWS IAM used for?

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.

What is the difference between an IAM user and an IAM role?

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.

Is AWS IAM free?

Yes. AWS does not charge for IAM users, groups, roles or policies. You pay for the AWS resources those identities use.

How does a backend service on AWS get permission to read an S3 bucket?

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.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders