SaveMyCert
Log in
5 of 5 free questions left today·for 30 a day
Security

DVA-C03 Authentication and Authorization: IAM, Cognito, STS and Signed URLs

26 min readDVA-C03 · SecurityUpdated

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.

What you’ll learn
  • 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

How authentication and authorization work for AWS applications

Every request to an AWS API is authenticated (who signed it, proven by a Signature Version 4 signature made with credentials) and then authorized (whether the policies in play allow this action on this resource). Application users are a separate layer: your app authenticates people with a user directory such as Amazon Cognito, and then either calls AWS on their behalf or exchanges their identity for temporary AWS credentials.

Principals and credentials

  • IAM users have long-term access keys. Developers should avoid them for applications: keys leak, need rotation and live forever.
  • IAM roles have no long-term credentials. A principal or service assumes the role through AWS STS and receives temporary credentials (access key ID, secret key and session token) that expire.
  • Service principals such as lambda.amazonaws.com, s3.amazonaws.com or sns.amazonaws.com represent an AWS service acting on your behalf.
  • Federated identities (Cognito identities, OIDC or SAML users) reach AWS only by assuming a role.

Policy types a developer meets

PolicyAttached toAnswers
Identity-based policyUser, group or roleWhat can this principal do, and on which resources?
Resource-based policyResource (bucket policy, Lambda function policy, SQS queue policy, KMS key policy, API Gateway resource policy, DynamoDB table policy)Which principals may act on this resource?
Trust policyRole (a special resource-based policy)Who may assume this role?
Permissions boundary, SCP, session policyUser/role, account, sessionThe maximum permissions; they never grant anything themselves

Evaluation in one paragraph

Everything starts as an implicit deny. An explicit Deny in any applicable policy always wins. Within one account, an Allow in either the identity policy or the resource policy is enough (KMS key policies are the exception: the key policy must allow access, directly or by delegating to IAM policies). Across accounts, both sides must allow: the caller's identity policy in its own account and the resource policy in the resource's account.

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 poolIdentity pool
JobAuthentication: sign-up, sign-in, MFA, password policies, federation to external IdPsAuthorization to AWS: issue role credentials
OutputID token, access token, refresh token (JWTs)AWS access key, secret key and session token
Used byYour own APIs (API Gateway authorizers, your backend)AWS SDK calls signed with SigV4
IdentifierThe 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.

Identity federation: corporate, social and workload identities

Identity federation lets people or workloads authenticated by another identity provider use your app or AWS without a separate password or access key. To let users sign in with a corporate or social identity, add the provider to the user pool: Cognito supports SAML 2.0, OpenID Connect and social providers (Google, Facebook, Login with Amazon, Sign in with Apple). The user pool's managed login pages (or the classic hosted UI) redirect to the provider, consume its assertion or token, create a federated user profile in the pool and issue the same user pool ID and access tokens that native users get. That is why federating at the user pool needs no changes to APIs that already trust the user pool's JWTs.

Where you add the IdPWhat the app receivesUse it when
Cognito user poolUser pool JWTsYour app and APIs work with user pool tokens; you want one token format for every user
Cognito identity poolTemporary AWS credentialsUsers must call AWS services directly; the identity pool can also trust a user pool, OIDC, SAML or social providers
IAM identity provider (SAML or OIDC)Temporary AWS credentials via AssumeRoleWithSAML or AssumeRoleWithWebIdentityWorkloads or tools that need AWS credentials, not app user tokens

Attribute mapping copies the provider's claims (email, name, groups) into user pool attributes, so group membership from a corporate IdP can drive the group-to-role mapping described above.

One person, two profiles

Cognito does not merge users by email address. A user who registered with a password and later chooses Google gets a separate federated user (for example Google_1234) with a different sub, so data stored under the first sub looks missing. To link them, call AdminLinkProviderForUser with the provider name and the external user's subject, pointing at the existing native user, typically from a pre sign-up Lambda trigger before the federated profile is created. After linking, the Google sign-in authenticates as the existing user. Linking is always an explicit call; Cognito never infers it from matching attribute values.

Workload federation with IAM OIDC providers (CI/CD)

Web identity federation lets a workload that holds an OIDC token from a trusted issuer exchange it for temporary AWS credentials with sts:AssumeRoleWithWebIdentity, so no long-term access key is stored anywhere. The standard example is a CI/CD system: GitHub Actions can mint an OIDC token for each job.

  1. Create an IAM OIDC identity provider for the issuer (for GitHub, token.actions.githubusercontent.com, audience sts.amazonaws.com).
  2. Create a deploy role whose trust policy names that provider as a Federated principal for sts:AssumeRoleWithWebIdentity.
  3. The workflow requests a token and assumes the role (for example with the aws-actions/configure-aws-credentials action), receiving short-lived credentials.

The point of federation is that no long-term credential exists at all, so there is nothing to store, rotate or leak. Match the STS call to what the identity provider issues: CI/CD platforms and Kubernetes issue OIDC tokens (AssumeRoleWithWebIdentity), while enterprise IdPs that sign people in through a browser issue SAML assertions (AssumeRoleWithSAML).

Scope the trust policy with claims

An aud condition alone is not enough: every GitHub workflow anywhere can obtain a token with that audience. Restrict the sub claim, which GitHub signs and which encodes the repository and trigger:

"Condition": {
  "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
  },
  "StringLike": {
    "token.actions.githubusercontent.com:sub": "repo:acme/billing-service:ref:refs/tags/v*"
  }
}

This example admits only jobs triggered by a version tag (one starting with v) in one repository. The sub format depends on what triggered the job (a branch or tag ref, a pull request, or a deployment environment), so the condition must match the form your workflow actually produces. The condition operator matters as much as the value: StringEquals compares literally, while StringLike expands * and ?. Trust conditions are only as strong as the claims they check, so build them from values the issuer signs into the token.

Controlling access to APIs: API Gateway authorizers

API Gateway offers four ways to authenticate callers, and the right one depends on who issues the caller's credentials and whether you need custom logic.

MechanismAPI typeCaller sendsChoose it when
Cognito user pool authorizerRESTA user pool ID or access tokenUsers sign in with a Cognito user pool and you want no authorization code
JWT authorizerHTTPA JWT from any OIDC/OAuth 2.0 issuer (including Cognito)Standards-based tokens validated by issuer and audience, no code
Lambda authorizer (TOKEN or REQUEST)REST (HTTP APIs also support Lambda authorizers)Anything: bearer token, headers, query stringsThird-party or custom tokens, or decisions that need data (a DynamoDB lookup, tenant rules)
IAM authorization (AWS_IAM)REST and HTTPA SigV4-signed requestCallers are AWS principals: services, roles, Cognito identity pool users

A JWT authorizer validates each token's signature against the issuer's public keys (fetched from its JWKS endpoint and cached), and checks iss, aud/client_id, exp, nbf, iat and any scopes configured on the route. A Lambda authorizer returns an IAM policy and a principalId (plus optional context) and is the only option that runs arbitrary code before the integration. API keys and usage plans are for throttling and quotas, not authentication. Mutual TLS authenticates client certificates, not user tokens.

ID tokens, access tokens and scopes

A Cognito authorizer accepts either token type until you configure OAuth scopes on a method. Then API Gateway requires a token whose scope claim contains one of them, and only the access token carries scopes, so clients must send the access token. The scope must also be allowed on the app client for it to appear in the access token. Refresh tokens and identity pool identity IDs are never accepted in the Authorization header.

Lambda authorizer caching

  • TOKEN authorizer: the cache key is the token (from one header), and an optional token validation regular expression rejects malformed tokens before the function runs.
  • REQUEST authorizer: the cache key is the combination of the configured identity sources (headers, query strings, stage variables, context). With caching on, if any identity source is missing, API Gateway returns 401 Unauthorized without invoking the function.
  • A cached result is keyed by identity, not by route: during the TTL, API Gateway evaluates the same returned policy for every method and resource that the cache key reaches, without calling the function again. Design the policy for the identity, describing every resource it may call with wildcard ARNs such as arn:aws:execute-api:eu-west-1:111122223333:a1b2c3/prod/GET/reports/*. A TTL of 0 turns caching off, so every request runs the function.

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:LeadingKeys compares 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:Attributes and dynamodb:Select restrict 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:

ConditionEffect 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):

  1. Credentials passed explicitly in code (client constructor or parameters)
  2. Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
  3. Web identity token settings (AWS_WEB_IDENTITY_TOKEN_FILE + AWS_ROLE_ARN, used for example by Amazon EKS service accounts)
  4. Shared config and credentials files (~/.aws/credentials, ~/.aws/config) and their profiles, including SSO and role profiles
  5. Container credentials (the ECS task role endpoint)
  6. 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

ComputeRole the code usesTrusted service principal
Amazon EC2Role in an instance profile, served by instance metadataec2.amazonaws.com
AWS LambdaExecution role; Lambda injects its credentials into the environmentlambda.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

  1. 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).
  2. Account A allows that principal sts:AssumeRole on the account B role ARN.
  3. The code calls AssumeRole and 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?

APIWho calls itResult
AssumeRoleAny IAM principal (user or role session)Credentials for a role, possibly in another account
AssumeRoleWithWebIdentityHolder of an OIDC token (Cognito identity pool, GitHub Actions, EKS)Role credentials; no AWS credentials needed to call it
AssumeRoleWithSAMLHolder of a SAML assertionRole credentials
GetSessionTokenAn IAM user (or root), often with MFATemporary credentials for the same user in the same account; cannot be called with role credentials
GetFederationTokenAn IAM user, typically a proxy appCredentials 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 allows lambda:InvokeFunction for the service principal, scoped with aws:SourceArn and aws: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:SendMessage for sns.amazonaws.com with aws:SourceArn equal to the topic ARN. If the queue uses a customer managed KMS key, the key policy must also allow sns.amazonaws.com kms:GenerateDataKey* and kms: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 it kms:Decrypt; and the principal's identity policy in A allows both actions. (Writes need kms:GenerateDataKey; multipart uploads need kms:Decrypt as well.)
  • API Gateway REST API with AWS_IAM: the API's resource policy in account A allows the caller's role execute-api:Invoke, and the caller's identity policy in account B allows execute-api:Invoke on 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.

Presigned S3 URLs, CloudFront signed URLs and signed cookies

A presigned or signed URL gives someone without AWS credentials secure, time-limited access to private content: the URL carries a signature made by a principal (S3) or a key (CloudFront), so the object or bucket can stay private.

S3 presigned URLs

Your backend, holding credentials with the needed permission, signs a URL for one operation on one key: GET to download, PUT to upload. Anyone with the URL acts with the signer's permissions until it expires. A presigned PUT lets browsers or mobile apps upload large files straight to S3, so your servers only issue URLs. Bucket CORS is still needed for browser uploads, but CORS never grants permission.

  • Expiry: up to 7 days when signed with an IAM user's long-term keys (SigV4). When signed with temporary credentials (a Lambda execution role, an instance role, an STS session), the URL stops working when those credentials expire, whatever ExpiresIn says.
  • Signer's permissions are checked at use: if the signer lacks a permission, the URL fails. For a bucket that encrypts with SSE-KMS, the signer needs kms:GenerateDataKey on the key to upload (and kms:Decrypt to download) besides the S3 action.
  • Headers that were signed (such as Content-Type) must be sent exactly as signed, or S3 returns SignatureDoesNotMatch.

CloudFront signed URLs and signed cookies

For content served through CloudFront, turn on restrict viewer access on the cache behavior and add a trusted key group holding your public key; the application signs with the matching private key (keep it in a secret store). CloudFront then rejects requests without a valid signature. Close the back door too: use origin access control (OAC) and a bucket policy that allows only that distribution, so nobody can bypass CloudFront and read S3 directly. OAC works with an S3 REST origin, not a static website endpoint.

Signed URLSigned cookies
CoversOne URL (or several with a custom-policy wildcard, but the signature lives in each URL)Many files without changing their URLs
Best forIndividual downloads; clients that don't support cookiesVideo streams (HLS/DASH playlists with hundreds of segments), whole subscriber areas
Canned policyCustom policy
Expiry (end time)YesYes
Start time (valid from)NoYes
Source IP rangeNoYes
Wildcards in the resourceNo (one exact URL)Yes

CloudFront signatures are made with your own key pair rather than IAM credentials, so their lifetime is whatever the policy says, independent of any role session.

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_IAM authorization 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 without execute-api:Invoke also gets 403.
  • aws sts get-caller-identity shows 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.

Key takeaways
  • 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.

Test yourself on this topic
DVA-C03 Security practice questions, with full explanations.
Practice now

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?