DVA-C03 Authentication and Authorization: IAM, Cognito, STS and Signed URLs
Authentication and authorization for AWS applications is the set of mechanisms that prove who a caller is (Amazon Cognito sign-in, federated identity providers, SigV4-signed requests) and decide what that caller may do (IAM identity, resource and trust policies, API Gateway authorizers, presigned and signed URLs). This lesson covers DVA-C03 Task 2.1 from a developer's point of view: federating users and workloads, controlling access to APIs and data, giving code temporary credentials instead of access keys, assuming roles, writing policies for principals and resources, and sharing private content for a limited time.
On this page10 sections
- How authentication and authorization work for AWS applications
- Amazon Cognito user pools vs identity pools
- Identity federation: corporate, social and workload identities
- Controlling access to APIs: API Gateway authorizers
- Fine-grained permissions: policy variables, ABAC and MFA
- Configuring programmatic access: credentials, profiles and service roles
- Assuming an IAM role: trust, cross-account access and session limits
- Identity-based vs resource-based policies, service principals and cross-account access
- Presigned S3 URLs, CloudFront signed URLs and signed cookies
- Making authenticated calls to AWS services
- Choose between Cognito user pools, identity pools and IAM identity providers, and federate SAML, OIDC, social and CI/CD identities without long-term keys
- Pick the right API Gateway access control (Cognito, JWT, Lambda or IAM authorizer) and avoid token-type, scope and caching mistakes
- Write fine-grained policies with Cognito policy variables, dynamodb:LeadingKeys, ABAC tags and MFA conditions
- Give code credentials through roles and the SDK credential provider chain, and grant service roles correctly, including iam:PassRole
- Assume IAM roles across accounts and handle trust policies, session duration and role chaining
- Decide when a resource-based policy is needed for service principals and cross-account access, including KMS key policies
- Share private content with S3 presigned URLs, CloudFront signed URLs or signed cookies, and make SigV4-authenticated calls
Amazon Cognito user pools vs identity pools
A user pool is a user directory that signs people in and issues JSON Web Tokens (JWTs); an identity pool (federated identities) exchanges a token from a trusted provider, or nothing at all for guests, for temporary AWS credentials from an IAM role. Apps that let users call AWS services directly (upload to S3, write to DynamoDB) without a backend usually need both.
| User pool | Identity pool | |
|---|---|---|
| Job | Authentication: sign-up, sign-in, MFA, password policies, federation to external IdPs | Authorization to AWS: issue role credentials |
| Output | ID token, access token, refresh token (JWTs) | AWS access key, secret key and session token |
| Used by | Your own APIs (API Gateway authorizers, your backend) | AWS SDK calls signed with SigV4 |
| Identifier | The user's sub claim (a UUID in the directory) | An identity ID such as us-east-1:1a2b... |
AWS services such as S3 do not accept JWTs; a request carrying an ID token in an Authorization header is not a valid AWS request. The app must exchange the token at the identity pool (GetId, then GetCredentialsForIdentity, which the SDKs and Amplify do for you) and sign requests with the returned credentials.
Authenticated and unauthenticated (guest) roles
An identity pool has an authenticated role for signed-in users and, when guest access is turned on, an unauthenticated role for visitors who have not signed in. Guest access is the no-backend way to give anonymous users narrow, read-only access (for example s3:GetObject on one prefix) while the bucket stays private. With guest access off, calling GetId without a login token does not produce an identity.
The role trust policy
Both roles trust cognito-identity.amazonaws.com for sts:AssumeRoleWithWebIdentity, and two conditions keep them safe:
"Condition": {
"StringEquals": {"cognito-identity.amazonaws.com:aud": "us-east-1:1a2b3c4d-..."},
"ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "authenticated"}
}
The aud condition restricts the role to this identity pool (without it, any identity pool could request it). The amr condition restricts it to signed-in identities (unauthenticated is the value for guests). The sub key in a Cognito token is the per-identity ID, not the pool ID.
Choosing different roles for different users
An identity pool can give users different roles. With role-based access control, you assign an IAM role ARN to a user pool group; members' ID tokens then carry the cognito:roles and cognito:preferred_role claims. In the identity pool, set the authenticated role selection to choose role from token (or use rules that map claim values to roles); otherwise everyone receives the default authenticated role. Groups reference roles; they cannot hold inline IAM policies, and the target roles must trust the identity pool.
Fine-grained permissions: policy variables, ABAC and MFA
Fine-grained access means one policy that gives each caller access only to their own data, written with policy variables and condition keys instead of one statement per user.
Per-user data with Cognito identities
The policy variable ${cognito-identity.amazonaws.com:sub} resolves to the caller's identity pool identity ID, not the user pool sub claim. Store and prefix data with the identity ID returned by the identity pool, or no key will ever match.
- DynamoDB:
dynamodb:LeadingKeyscompares the partition key of each request with a value, so"ForAllValues:StringEquals": {"dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]}limits users to items whose partition key is their identity ID.dynamodb:Attributesanddynamodb:Selectrestrict attributes, not items. - S3: a resource such as
arn:aws:s3:::app-data/uploads/${cognito-identity.amazonaws.com:sub}/*gives each user one prefix.
${aws:username} exists only for IAM users; it is not defined for role sessions or federated users.
Attribute-based access control (ABAC)
ABAC grants access when tags match: "StringEquals": {"aws:ResourceTag/team": "${aws:PrincipalTag/team}"}. Principal tags can come from the role or from session tags passed by an IdP. New teams need no policy edits because the comparison is generic. Know the three tag keys: aws:ResourceTag (tags on the existing resource), aws:RequestTag (tags being set in this request) and aws:PrincipalTag (tags on the caller).
Security tokens and MFA
Requests signed with a long-term access key cannot carry MFA. To use MFA from the CLI or SDK, call sts:GetSessionToken (or AssumeRole) with SerialNumber and TokenCode; the returned temporary credentials carry MFA context. In a CLI role profile, mfa_serial makes the CLI prompt for a code when it assumes the role.
Write MFA conditions carefully:
| Condition | Effect in a Deny statement |
|---|---|
"Bool": {"aws:MultiFactorAuthPresent": "false"} | Misses long-term key requests, where the key is absent |
"BoolIfExists": {"aws:MultiFactorAuthPresent": "false"} | Denies both key-absent requests and sessions created without MFA: the usual choice |
"Null": {"aws:MultiFactorAuthPresent": "true"} | Denies only requests where the key is absent (long-term keys) |
aws:MultiFactorAuthAge (seconds since MFA) can require recent MFA, but numeric comparisons do not match when the key is missing, so pair it with an IfExists form or a presence check.
Configuring programmatic access: credentials, profiles and service roles
Applications on AWS should get credentials from a role attached to their compute, never from access keys in code, files or environment variables; the AWS SDKs find those credentials automatically through the default credential provider chain.
The default credential provider chain
The SDKs and CLI look in a fixed order and use the first source that has credentials (details vary slightly by SDK):
- Credentials passed explicitly in code (client constructor or parameters)
- Environment variables:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN - Web identity token settings (
AWS_WEB_IDENTITY_TOKEN_FILE+AWS_ROLE_ARN, used for example by Amazon EKS service accounts) - Shared config and credentials files (
~/.aws/credentials,~/.aws/config) and their profiles, including SSO and role profiles - Container credentials (the ECS task role endpoint)
- EC2 instance metadata (the instance profile role)
Because earlier sources win, stray environment variables or a leftover credentials file silently override the instance or task role; an AccessDenied naming an IAM user instead of the role is the tell. Remove the overriding source rather than changing the role.
Roles for each compute service
| Compute | Role the code uses | Trusted service principal |
|---|---|---|
| Amazon EC2 | Role in an instance profile, served by instance metadata | ec2.amazonaws.com |
| AWS Lambda | Execution role; Lambda injects its credentials into the environment | lambda.amazonaws.com |
| Amazon ECS (EC2 or Fargate) | Task role (taskRoleArn) | ecs-tasks.amazonaws.com |
ECS has a second role that application code never uses: the task execution role (executionRoleArn) lets the ECS agent pull images from Amazon ECR, fetch secrets for the task definition and send logs. Permissions for your SDK calls belong on the task role. Fargate has no container instance role. The managed policy AWSLambdaBasicExecutionRole grants only CloudWatch Logs permissions; every other service the function calls needs its own statement listing those actions on those resource ARNs.
Policies name principals by ARN: an account, an IAM user, a role or role session, a federated provider or a service principal. Compute resources themselves are not principals; their code acts as the role attached to them.
Developer machines: CLI profiles
Prefer IAM Identity Center (SSO) profiles or roles over long-term keys. A role profile tells the CLI which role to assume and which credentials to use for the call:
[profile deploy]
role_arn = arn:aws:iam::444455556666:role/deploy-role
source_profile = dev
# mfa_serial = arn:aws:iam::111122223333:mfa/dev-user (optional)
The CLI calls STS, caches the temporary credentials and refreshes them automatically. Use credential_source (Ec2InstanceMetadata, EcsContainer, Environment) instead of source_profile when the source credentials come from the compute environment. external_id is only needed when the role's trust policy requires one.
Passing roles to services: iam:PassRole
Whenever a principal hands a role to a service (a Lambda execution role in CreateFunction, an ECS task role, an EC2 instance profile, a CloudFormation service role), IAM checks iam:PassRole on that role. This stops users from giving a service more access than they have. The least-privilege grant names the specific role ARN and adds "StringEquals": {"iam:PassedToService": "lambda.amazonaws.com"}. A wildcard resource such as role/* works but over-grants; sts:AssumeRole is a different operation.
Assuming an IAM role: trust, cross-account access and session limits
Assuming a role means calling AWS STS to get temporary credentials for that role; it succeeds only when the role's trust policy allows the caller and the caller's own permissions allow sts:AssumeRole on the role (across accounts both are required; in the same account a trust policy naming the specific principal ARN is enough on its own). The role's permissions policy then decides what the session can do; it has nothing to do with who may assume the role.
Cross-account access pattern
- Account B creates a role with the permissions it chooses to grant, and a trust policy naming the account A principal (for example a Lambda execution role ARN).
- Account A allows that principal
sts:AssumeRoleon the account B role ARN. - The code calls
AssumeRoleand creates SDK clients with the returned credentials (or uses a refreshing credentials provider).
This keeps the resource owner in control and avoids IAM users and access keys. For a third party assuming a role in your account, the trust policy should also require an sts:ExternalId condition, which protects against the confused deputy problem.
Which STS call?
| API | Who calls it | Result |
|---|---|---|
AssumeRole | Any IAM principal (user or role session) | Credentials for a role, possibly in another account |
AssumeRoleWithWebIdentity | Holder of an OIDC token (Cognito identity pool, GitHub Actions, EKS) | Role credentials; no AWS credentials needed to call it |
AssumeRoleWithSAML | Holder of a SAML assertion | Role credentials |
GetSessionToken | An IAM user (or root), often with MFA | Temporary credentials for the same user in the same account; cannot be called with role credentials |
GetFederationToken | An IAM user, typically a proxy app | Credentials for a federated user in the same account, scoped by a session policy |
Session duration and role chaining
DurationSeconds can be from 15 minutes up to the role's maximum session duration (1 to 12 hours, set on the role). Role chaining (assuming a role with credentials that are already a role session, which includes any call from a Lambda function, ECS task or EC2 instance role) is limited to one hour: a chained request for more than 3600 seconds fails; it is not shortened, and raising either role's maximum does not help. For longer jobs, request an hour or less and assume the role again before the credentials expire; many SDKs offer an assume-role credentials provider that refreshes automatically.
Identity-based vs resource-based policies, service principals and cross-account access
Identity-based policies say what a principal may do; resource-based policies say who may act on a resource. A developer needs a resource-based policy whenever another service or another account is the caller, because there is no identity policy of yours attached to that caller.
Services calling your resources
- S3, SNS, EventBridge or API Gateway invoking Lambda: add a statement to the function's resource-based policy (
lambda:AddPermission) that allowslambda:InvokeFunctionfor the service principal, scoped withaws:SourceArnandaws:SourceAccount. S3 checks this when you save an event notification and fails with "Unable to validate the following destination configurations" if it is missing. The execution role covers the opposite direction: what the function may call. - SNS delivering to SQS: the queue policy must allow
sqs:SendMessageforsns.amazonaws.comwithaws:SourceArnequal to the topic ARN. If the queue uses a customer managed KMS key, the key policy must also allowsns.amazonaws.comkms:GenerateDataKey*andkms:Decrypt. AWS managed keys (aws/service) have key policies you cannot edit, so whenever a different service principal must use the key, the queue needs a customer managed key.
The aws:SourceArn/aws:SourceAccount conditions stop a service acting for someone else's resource from using your permission (the confused deputy problem).
Cross-account access without assuming a role
When a principal in account A calls a resource in account B directly, both accounts must allow the request:
- S3 objects encrypted with SSE-KMS: the bucket policy in B allows the A principal
s3:GetObject; the key policy in B allows itkms:Decrypt; and the principal's identity policy in A allows both actions. (Writes needkms:GenerateDataKey; multipart uploads needkms:Decryptas well.) - API Gateway REST API with
AWS_IAM: the API's resource policy in account A allows the caller's roleexecute-api:Invoke, and the caller's identity policy in account B allowsexecute-api:Invokeon the method ARNs. Within one account, the identity policy alone is enough. - DynamoDB supports resource-based policies on tables (a table's policy also covers its indexes) and on streams, so cross-account table access can be granted the same way.
The alternative is a role in the resource account that the caller assumes; it centralises control in that account but adds a role to manage.
Permissions boundaries
A permissions boundary is a managed policy set on a user or role that caps its identity-based permissions: an action must be allowed by both the identity policy and the boundary. Teams use boundaries to let developers create roles for their functions without being able to create roles more powerful than allowed.
Making authenticated calls to AWS services
An authenticated AWS call is an HTTPS request signed with Signature Version 4 (SigV4): a hash of the request, the date, the Region and the service name, signed with the caller's secret key, plus the session token for temporary credentials. The AWS SDKs and CLI sign every request for you; you sign manually only when calling an endpoint with plain HTTP, such as an IAM-protected API.
IAM-authorized API Gateway methods
For a method with AWS_IAM authorization, two checks happen. Authentication: the request must be SigV4-signed for the service execute-api; an unsigned request is rejected with 403 and a Missing Authentication Token message. Authorization: the signing principal needs execute-api:Invoke on the method ARN (arn:aws:execute-api:region:account:api-id/stage/METHOD/path), from its identity policy or the API's resource policy. IAM keeps the two jobs under separate service prefixes: apigateway actions control the management API (creating and changing APIs), and execute-api controls calling a deployed one.
Lambda function URLs
A function URL uses auth type AWS_IAM or NONE. With AWS_IAM, requests must be SigV4-signed with the service name lambda, and the caller needs lambda:InvokeFunctionUrl; for function URLs created from October 2025 it also needs lambda:InvokeFunction (in its identity policy for a same-account caller, or through the function's resource-based policy). The execution role is the function's own identity and plays no part in who may call it. Switching to NONE makes the URL public.
Clock skew and signature errors
The signature includes a timestamp, and AWS rejects requests whose time is too far from its own (S3 returns RequestTimeTooSkewed; other services return signature-expired errors). On EC2 the fix is time synchronisation with the Amazon Time Sync Service at 169.254.169.123 (chrony is configured for it on Amazon Linux). Every signature embeds the host's clock, so the error persists until the clock itself is corrected. SignatureDoesNotMatch usually means a wrong secret key, a wrong service or Region in the signature, or a request changed after signing.
Reading the error
- AccessDenied / AccessDeniedException names the calling principal: check that it is the identity you expected, then the identity policy, resource policy, boundaries and explicit denies.
- API Gateway status codes depend on the authorizer. With Cognito, JWT and Lambda authorizers, 401 means authentication failed (missing or invalid token, missing identity source) and 403 means the authorizer's policy or a resource policy denied the request. With
AWS_IAMauthorization there is no 401: a missing signature returns 403 Missing Authentication Token, an invalid signature returns 403 with a signature error, and a valid signer withoutexecute-api:Invokealso gets 403. aws sts get-caller-identityshows which credentials the CLI or SDK is actually using; it returns no credentials and needs no permissions.
Tip. Task 2.1 questions are developer scenarios: an app needs users signed in, a pipeline must stop using access keys, a call returns AccessDenied, 401 or 403, or a link to private content expires too early. Most items set a constraint (no long-term credentials, no backend compute, no authorization code, least privilege, no new roles, the bucket stays private) and offer several mechanisms that would almost work, so you must know exactly what each one does: which Cognito component issues tokens and which issues AWS credentials, which token an authorizer expects, which role a service uses, which side of a cross-account call needs a policy, and how long a signed URL really lives. Expect IAM policy JSON, trust-policy conditions and CLI configuration snippets, and multiple-response items that ask for every change a fix needs.
- User pools authenticate people and issue JWTs; identity pools exchange tokens (or guest access) for temporary AWS credentials. AWS services accept only SigV4-signed requests, never JWTs.
- ${cognito-identity.amazonaws.com:sub} is the identity pool identity ID, not the user pool sub claim; use it with dynamodb:LeadingKeys or S3 prefixes for per-user access.
- Federate CI/CD with an IAM OIDC provider and AssumeRoleWithWebIdentity, and pin the token's sub claim in the trust policy; an aud condition alone admits every workflow on the platform.
- Cognito authorizer for user pool tokens on REST APIs, JWT authorizer for OIDC tokens on HTTP APIs, Lambda authorizer for custom logic, IAM authorization for AWS principals; API keys are not authentication.
- Lambda authorizer policies are cached per key across methods: return a policy for everything the caller may access, and remember a missing REQUEST identity source yields 401 without an invocation.
- SDKs use the first credentials in the provider chain, so environment variables or files override instance and task roles; ECS application calls use the task role, not the task execution role.
- AssumeRole needs the role's trust policy and the caller's permission; chained role sessions are capped at one hour and longer requests fail.
- Across accounts both the identity policy and the resource policy (bucket, key, queue, API) must allow the call; services invoking your resources need resource-based policies scoped with aws:SourceArn.
- S3 presigned URLs act with the signer's permissions and die with the signer's temporary credentials; CloudFront signed URLs or cookies with a trusted key group plus origin access control protect content served through CloudFront.
- Use BoolIfExists on aws:MultiFactorAuthPresent in MFA deny statements, and get MFA-backed temporary credentials with GetSessionToken or AssumeRole plus SerialNumber and TokenCode.
Frequently asked questions
What is the difference between an Amazon Cognito user pool and an identity pool?
An Amazon Cognito user pool is a user directory: it signs users up and in, supports MFA and external identity providers, and issues JSON Web Tokens (ID, access and refresh tokens) that your own APIs can validate. An identity pool exchanges a token from a trusted provider, such as a user pool, or a guest request for temporary AWS credentials from an IAM role, so the app can call AWS services such as Amazon S3 or DynamoDB directly with SigV4-signed requests. Apps that need both sign-in and direct AWS access use the two together.
Why does my S3 presigned URL expire before the time I set?
An S3 presigned URL stops working when the credentials that signed it expire, even if its ExpiresIn value is longer. URLs signed by a Lambda execution role, an EC2 instance role or any other temporary STS session therefore last only as long as that session. Only URLs signed with an IAM user's long-term keys can last up to the 7-day SigV4 maximum. If you need long-lived links without IAM user keys, serve the content through Amazon CloudFront and use CloudFront signed URLs, whose expiry is set by the policy you sign with your own key pair.
When should I use CloudFront signed cookies instead of signed URLs?
Use CloudFront signed cookies when one user needs access to many files without changing their URLs, for example an HLS or DASH video made of a playlist and hundreds of segments, or a whole members-only area of a site. Use signed URLs for individual files or for clients that do not support cookies. Both use a trusted key group to verify signatures, and both support canned policies (expiry only) or custom policies (start time, IP range, wildcard resources).
What is the difference between the ECS task role and the task execution role?
The ECS task role (taskRoleArn) supplies the credentials that your application code uses for AWS SDK calls, served to containers through the ECS container credentials endpoint. The task execution role (executionRoleArn) is used by the ECS agent on your behalf to pull container images from Amazon ECR, retrieve secrets referenced in the task definition and send logs to CloudWatch Logs. Permissions such as dynamodb:PutItem for your application belong on the task role, which must trust ecs-tasks.amazonaws.com.
Why does a Lambda function fail when it assumes a role for more than one hour?
A Lambda function's credentials are already a role session, so its sts:AssumeRole call is role chaining, and AWS limits role-chained sessions to one hour. A request with DurationSeconds above 3600 fails with a validation error instead of being shortened, regardless of either role's maximum session duration setting. Long-running jobs should request one hour or less and assume the role again before the credentials expire, for example with an SDK credentials provider that refreshes automatically.
How do I let GitHub Actions deploy to AWS without storing access keys?
Create an IAM OIDC identity provider for token.actions.githubusercontent.com with the audience sts.amazonaws.com, then create a deployment role whose trust policy allows sts:AssumeRoleWithWebIdentity for that provider. Add a condition on the token's sub claim, which GitHub signs and which encodes the repository plus the branch, tag, pull request or environment that triggered the job, so only the workflows you intend can assume the role. Each job requests an OIDC token and exchanges it for short-lived credentials, so no long-term key is stored in GitHub.
What does an external ID do in an IAM role trust policy?
An external ID is a value that a third party must pass in the ExternalId parameter of sts:AssumeRole, enforced by an sts:ExternalId condition in the role's trust policy. It protects against the confused deputy problem: a vendor that assumes roles in many customer accounts cannot be tricked by one customer into using a role that belongs to another, because each customer's role expects its own external ID. You only need it when an outside party assumes your role; roles used inside your own organization rely on the principal ARN in the trust policy.
What is iam:PassRole and when is it checked?
iam:PassRole is the permission to hand an existing IAM role to an AWS service, for example when creating a Lambda function with an execution role, registering an ECS task definition with a task role, launching an EC2 instance with an instance profile or deploying a CloudFormation stack with a service role. IAM checks it on the principal making the call, so users cannot give a service more access than they could use themselves. A least-privilege grant names the specific role ARN and can add an iam:PassedToService condition for the service that will receive it.
Source
This lesson covers the "Security" domain of the official DVA-C03 exam guide. Vendors revise their guides — check the source for the current version.
- AWS Certified Developer – Associate (DVA-C03) exam guide — Amazon Web Services
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.