Business Challenge
An identity pool is the thing that turns a login into AWS credentials. “Amazon Cognito… delivers temporary, limited-privilege credentials to your application to access AWS resources.”
“Amazon Cognito has two different flows for authentication with public providers: enhanced and basic.” They reach the same destination, and the difference between them is one sentence long.
Enhanced: “Enhanced authentication manages the logic of IAM role selection and credentials retrieval in your identity pool configuration.”
Basic: “When you implement the basic authentication flow, your application selects the IAM role that you want users to assume.”
AWS states the scope of that plainly, and it is broader than "the role you configured":
“The AssumeRoleWithWebIdentity request in the classic workflow grants your app a
greater ability to request credentials for any AWS Identity and Access Management role that
you have configured with a sufficient trust policy.”
Read that as an inventory question rather than a feature description. The set of roles your client can
obtain is not the set you intended it to use — it is the set whose trust policies happen to
accept a token from this identity pool. Any role in the account with a loose enough
cognito-identity.amazonaws.com trust relationship is reachable, whether or not anyone
thought of it as a mobile app role.
And the documentation is unusually direct about what that depends on: “To implement basic authentication, first evaluate the trust relationships of your IAM roles for web identities. Then build the logic for role selection into your client and secure the client against modification by users.”
"Secure the client against modification by users" is a remarkable sentence to find in a security guide, and it is the honest one. Role selection in the client is a decision made on a device you do not control.
FixPrefer the enhanced flow, which AWS calls “the most secure choice with the lowest level of developer effort”. If you are on the basic flow, the audit is not of your app — it is of every role in the account whose trust policy names cognito-identity.amazonaws.com. That list is your actual blast radius, and nothing in the identity pool shortens it.
This is the detail that makes the design coherent, and it is easy to miss because it appears as an error string:
“When you attempt GetOpenIdToken in an identity pool with role mappings, you
receive the following error. Basic (classic) flow is not supported with RoleMappings, please
use enhanced flow.”
And the corollary, stated as a property of pools that do support the basic flow: “You can sign in with the Basic authflow in user pools that don't have role mappings. This type of identity pool doesn't have a default authenticated or unauthenticated role, and doesn't have role-based or attribute-based access control configured.”
So the two models are mutually exclusive by enforcement rather than by convention. You cannot configure careful pool-side role mapping and then let a client select its own role — which matters because that configuration would look like a control while controlling nothing.
This is the service refusing to let you hold a false belief. The alternative design, where both are configured and the client silently wins, is the one that produces an audit finding two years later.
FixTreat the choice as architectural and made once. If your pool has role mappings, the basic flow is not available to you and your role-selection logic belongs in the pool. If your pool has no mappings, understand that there is no default role either — nothing is deciding on your behalf.
Guest access is a first-class configuration choice, not an absence of one: “In Configure identity pool trust, choose to set up your identity pool for Authenticated access, Guest access, or both”, and then “In Configure permissions, choose a default IAM role for authenticated or guest users in your identity pool.”
So an unauthenticated caller does not get no credentials. It gets credentials from a role: “You can also define a separate IAM role with limited permissions for guest users who are not authenticated.”
What separates the two at the IAM layer is one condition in the trust policy. AWS's example restricts
the aud to the pool, and then:
“the policy specifies that one of the array members of the multi-value amr
claim of the token issued by the Amazon Cognito GetOpenIdToken API action has the value
authenticated.”
Which means the authenticated role is protected from guest tokens by the presence of that
amr condition and by nothing else. Omit it and a guest token satisfies a trust policy
written for signed-in users.
Read the trust policy of your authenticated role and confirm the amr condition is there. AWS is explicit about the purpose: the trust policy exists so the role “can only be assumed by Amazon Cognito for authenticated users in your identity pool” — and both halves of that sentence, the pool restriction and the authenticated restriction, are separate conditions you have to have written.
Architecture
A token in, an identity ID in the middle, AWS credentials out — and a decision about where the role is chosen that changes the whole security model.
What the pool accepts, and the one asymmetry in it
| Provider | Authentication artifact |
|---|---|
| Amazon Cognito user pool | ID token |
| OpenID Connect (OIDC) | ID token |
| SAML 2.0 | SAML assertion |
| Social provider | Access token |
Three of those are assertions about identity. The fourth is a bearer credential for an API. That is not a defect — it is what social providers issue — but it is worth knowing that the artifact your pool trusts from a social login is a different kind of object from the one it trusts from OIDC.
The two flows, step for step
| Enhanced (simplified) | Basic (classic) | |
|---|---|---|
| Order of operations | GetId → GetCredentialsForIdentity |
GetId → GetOpenIdToken → AssumeRoleWithWebIdentity |
| Who selects the role | The pool — it “manages the logic of IAM role selection” | Your application |
| What bounds the choice | Pool configuration: default role, RBAC or ABAC | “any… role that you have configured with a sufficient trust policy” |
| Credential lifetime | “valid for one hour” | “You can also request a custom role session duration” |
| Calls AWS STS directly | No — “doesn't need to make additional API requests to AWS STS” | Yes |
| Works with role mappings | Yes | No — the request errors |
“GetOpenIdToken returns a new OAuth 2.0 token that is issued by your identity pool.”
That is the token STS evaluates, and it is why the trust policy conditions are written against cognito-identity.amazonaws.com and the pool's own aud and amr claims rather than against your upstream provider. The provider's assertion is consumed earlier and does not reach STS.
Where role selection can be configured, when the pool is doing it
“Permissions for each user are controlled through IAM roles that you create. You can define rules
to choose the role for each user based on claims in the user's ID token.” And
the enhanced flow's own mechanism:
“The GetCredentialsForIdentity request of the enhanced authflow requests a
role based on the contents of an access token.”
So in the enhanced flow the claims decide, evaluated server-side by the pool. In the basic flow the client decides and the claims only matter insofar as a trust policy condition tests them. Same claims, two completely different enforcement points.
“To allow a user to set roles with permissions in excess of the user's existing permissions on an identity pool, grant them iam:PassRole permission to pass the role to the set-identity-pool-roles API.”
Whoever can set the pool's roles can grant the pool's users more than that person holds. That is how PassRole is supposed to work, and it means the right to configure an identity pool is a privilege-escalation path unless the roles that can be passed are constrained.
One configuration choice that cannot be undone
“If you configure a Custom developer provider, you can't modify or delete it after you create your identity pool.”
Worth noting at creation time rather than discovering later, because the developer-authenticated flow is the one where the pool trusts you completely: “the claims that you present in your request for an identity are arbitrary identifiers, and the authentication is authorized by IAM credentials that you encode in your application.”
Why This Architecture Holds Up
Refusing to combine the two models is the best decision here
The error string is the whole argument: “Basic (classic) flow is not supported with RoleMappings, please use enhanced flow.”
A service that allowed both would let you configure role mapping, read it back in the console, show it to an auditor, and have it decide nothing because the client passes its own role ARN to STS. Making the combination an error means the configuration you can see is the configuration that applies. That is worth more than the flexibility it removes.
The enhanced flow is recommended on grounds that are actually about security
“The enhanced flow is the most secure choice with the lowest level of developer effort”, because “Your identity pool evaluates your users for the IAM role credentials that they should receive. You don't need to embed logic for role selection in your client.”
Moving a decision off an untrusted device and into a service is the actual security property, and AWS names it rather than selling the flow on request count. The request-count benefit is real and listed second.
Telling you to secure the client is better than implying you need not
“build the logic for role selection into your client and secure the client against modification by users” is an uncomfortable instruction, and it is the correct one. The basic flow's security depends on client integrity; saying so lets a reader decide whether that assumption holds for a mobile app or a single-page application. The alternative — documenting the flow as "more granular control" and stopping — would leave the dependency unstated.
It is paired with an explicit default: “When you create a new identity pool, don't activate basic (classic) authentication by default, as a best practice.”
Key Architecture Decisions
| Decision | Choice | Reasoning |
|---|---|---|
| Which flow | Enhanced, unless you have a specific reason | AWS calls it “the most secure choice with the lowest level of developer effort”. |
| If using the basic flow | Audit every role trusting cognito-identity.amazonaws.com |
The client can request “any… role… with a sufficient trust policy”. |
| Authenticated role trust policy | Require the amr condition explicitly |
It is what distinguishes an authenticated token from a guest one. |
Role trust policy aud |
Pin it to the identity pool ID | AWS's example restricts the aud “to match the identity pool”. |
| Guest access | Decide deliberately; it is a configured role | An unauthenticated identity receives credentials from a role you choose. |
| Role mappings plus basic flow | Not possible — pick one model | GetOpenIdToken returns an error on a pool with role mappings. |
Who may call set-identity-pool-roles |
Constrain it, and constrain what can be passed | iam:PassRole here can grant more than the caller holds. |
| Custom developer provider | Decide before creating the pool | You “can't modify or delete it after you create your identity pool”. |
| Session duration | One hour on enhanced; custom only on basic | A longer session is a reason some teams choose basic — weigh it against client-side selection. |
| Social provider logins | Note the artifact is an access token | Unlike OIDC and user pools, which present ID tokens. |
The audit worth running
List every IAM role in the account whose trust policy has
Federated: cognito-identity.amazonaws.com. For each one, check two conditions: is
the aud pinned to a specific pool, and is there an amr condition. A role missing
the first is reachable from any of your pools; a role missing the second is reachable by guests.
Then establish which flow each client uses. If any client uses the basic flow, the list above is the set of roles it can request — not the one role you had in mind. That is the number to put in front of whoever owns the risk.
Closing Thought
The interesting thing about identity pools is not that the basic flow is riskier — it is that AWS made the two models impossible to mix and said so with an error rather than a warning. You cannot have a role-mapping configuration that looks authoritative while a client chooses for itself. One or the other.
That decision is worth noticing because the series keeps finding the opposite shape. #84 had a filter that looked like a boundary and was not. #88 had a lifecycle attached to a handle rather than to the secret. Here, the one configuration that could have misled you is the one the service refuses to let you create.
What remains your job is the sentence about trust policies. In the basic flow the bound on what your app can request is not in the pool at all — it is distributed across every role in the account that happens to trust Cognito. That list is discoverable, and it is the only version of "what can this app get" that is true.
Security & Identity — AWS Network Access Analyzer: what a Network Access Scope actually asserts, why a finding is a reachable path rather than a misconfiguration, and how its answers differ from a security group review.
Official AWS Reference
- Identity pools authentication flow — the two flows and their order of operations, the artifacts accepted from each provider type, where role selection happens in each flow, the statement that the classic workflow can request any role with a sufficient trust policy, the one-hour credential lifetime and custom session duration, the RoleMappings error that blocks the combination, the properties of a pool without role mappings, the grounds on which the enhanced flow is recommended, and the instruction to secure the client against modification by users
- Role-based access control — rules choosing a role from ID token claims, the default authenticated role and the separate limited role for guests, the purpose and worked example of a role trust policy including the aud restriction and the amr authenticated condition, and the iam:PassRole note about setting roles with permissions in excess of your own
- Identity pools — configuring pool trust for authenticated access, guest access or both, choosing a default IAM role for authenticated or guest users, and the immutability of a custom developer provider once the pool exists
- Attributes for access control — further reading on the ABAC path through role selection; no claims in this post are drawn from it
- Developer-authenticated identities — further reading on the third flow; no claims in this post are drawn from it
Comments