CoderJony
HomeBlogAboutContact
CoderJony

Full-stack engineering, cloud architecture, and scalable systems

ankushjain358@gmail.com
© 2026 CoderJony. All rights reserved.
CoderJony
HomeBlogAboutContact
Back to Articles

Amazon Cognito User Pools: The Username Confusion Nobody Warns You About

A
Ankush Jain
May 23, 2026
Amazon CognitoAWS
Amazon Cognito User Pools: The Username Confusion Nobody Warns You About

A field guide to sub, username, alias mode, and UsernameAttributes - and how to build a user pool you won't regret later.


Amazon Cognito is a powerful service, but it has one sharp edge that trips up nearly every team that integrates it for the first time: the word username means at least two different things depending on where you encounter it. Get this wrong at design time, and you're looking at a migration later. Get it right, and everything else snaps into place.

This post unpacks the internals of how Cognito manages user identity, walks through the two fundamental user pool configurations, and gives you a concrete recommendation for greenfield projects.


The "username" field has a split personality

The field called username exists in two completely different contexts, and they behave differently.

Context 1 - Internal identity store. The value Cognito uses in its backend to index a user's record. Depending on pool configuration, this is either a value you provide at sign-up or a UUID Cognito generates automatically.

Context 2 - SDK / API parameter. The username field in calls like InitiateAuth or SignUp. The value you pass here is resolved differently based on how your user pool is configured. It might accept an email, a phone number, or a custom username string.

This duality is the root of most Cognito confusion. Let's build up the full picture from the ground level.


Key user attributes in Cognito

When a user is created in a Cognito User Pool, Cognito stores a set of attributes on their profile. The most important ones to understand are:

Attribute Description
sub A UUID assigned by Cognito at user creation. Immutable, never changes, even if the user updates their email or phone. The gold-standard identity anchor.
username A unique identifier string for the user within the pool. Behavior depends on pool configuration: either a custom value you supply, or a UUID Cognito auto-generates.
email The user's email address. Can be marked as verified. Used as a sign-in identifier in certain pool configurations.
phone_number The user's phone number in E.164 format. Can be verified. Used as a sign-in identifier in certain configurations.
preferred_username A display-friendly handle (e.g. @handle). Can be configured as an alias to allow sign-in. Not the same as the internal username.

sub vs username - the critical distinction

sub is Cognito's internal, permanent user identifier. Think of it as the primary key in Cognito's database. It is assigned at user creation, never modified, and persists for the life of the account. You should store sub as the foreign key in your own databases when linking records to a Cognito user.

username, on the other hand, is the login handle - the string used to look up a user in the pool. Its behavior is where the configuration complexity lives. In some pool setups it's something the user chooses (like a handle or their email address); in others, it's a UUID that Cognito auto-generates and the user never sees at all.

Rule of thumb: Never store username as a foreign key in your external databases. Always use sub. Email addresses change, phone numbers change, and even usernames can change. sub doesn't.


The two user pool modes

When you create a User Pool, you choose which attributes act as sign-in identifiers. The AWS Console presents this as a list of checkboxes: email, phone number, and username. Under the hood, your selection flips the pool into one of two fundamentally different modes.

Both are valid patterns, but they have meaningfully different trade-offs, and you cannot change the mode after pool creation.


Case A: Alias mode (username is selected)

In this configuration, users provide a username string at sign-up, this becomes the primary login handle stored in Cognito's backend. If you additionally select email or phone number as sign-in options, those become aliases.

What's an alias? An alias is an attribute value that can be entered in the username field of Cognito's sign-in APIs as an alternative to the actual username. Cognito resolves it under the hood and maps it to the correct user record. The API doesn't expose a separate "alias" field — you just put the alias value in the username parameter.

When you create this pool via the API or SDK, the configuration looks like this:

{
  "UsernameAttributes": [],
  "AliasAttributes": ["email", "phone_number"]
}

So a user might sign up with username john_doe, email john@example.com, and phone +14155550100. At sign-in time, all three values are valid inputs for the username parameter, Cognito normalizes them behind the scenes.

Important: Cognito enforces uniqueness on verified aliases. If email or phone number is enabled as an alias and a user has verified theirs, no other account can claim that same email or phone, even as a regular attribute. This prevents silent conflicts during sign-up.


Case B: UsernameAttributes mode (username not selected)

This is the cleaner architecture. When you don't select username as a sign-in identifier, Cognito automatically assigns a UUID as the internal username for every user. The user never sees or knows this UUID, they sign in using their email or phone number, which you designate as the actual login identifiers via UsernameAttributes.

{
  "UsernameAttributes": ["email", "phone_number"],
  "AliasAttributes": []
}

The terminology difference between the two parameters is subtle but important:

Parameter What it does Where it applies
AliasAttributes Designates attributes as alternative login handles alongside an existing user-defined username Case A (alias mode)
UsernameAttributes Designates attributes as the primary login identifier, replacing a user-defined username entirely Case B (UUID mode)

These two parameters are mutually exclusive. Setting both at the same time is invalid.


All the combinations at a glance- Very important

Depending on which sign-in identifiers you select in the Console, here are the possible configurations:

Selection Mode Internal username Valid sign-in values
Username only Alias User-defined string Username only
Username + email Alias User-defined string Username or email
Username + phone Alias User-defined string Username or phone
Email only UsernameAttributes Auto UUID Email only
Phone only UsernameAttributes Auto UUID Phone only
Email + phone UsernameAttributes Auto UUID Email or phone
Username + email + phone Alias User-defined string Username, email, or phone

Mental model: quick reference

Concept What it is
sub Immutable UUID. Always the true identity anchor. Store this in your DB.
username (alias mode) User-defined login handle stored in Cognito backend.
username (UsernameAttributes mode) Auto-generated UUID. User never sees it.
Alias Alternative value accepted in the username API parameter. Resolves to the actual user.
AliasAttributes Pool config. Adds email/phone as aliases alongside a user-defined username.
UsernameAttributes Pool config. Makes email/phone the primary identifier; internal username becomes a UUID.

Choosing the right configuration

UsernameAttributes with email or phone works cleanly for most cases, but selecting both email and phone together introduces a subtle inconsistency: at sign-up, the user can provide either one, which means some users have an email on file and others have only a phone. Your application then has to handle this uneven data everywhere.

The option that avoids this is AliasAttributes: ["email", "phone_number"] with a UUID you supply as the username at sign-up. Both email and phone are always collected, both work for sign-in, and the internal identity is a stable UUID you control.

Configuration Consistent data Both email + phone Hosted UI works Custom UI required
UsernameAttributes: ["email"] Yes No Yes No
UsernameAttributes: ["phone_number"] Yes No Yes No
UsernameAttributes: ["email", "phone_number"] No, user picks one Yes Yes No
AliasAttributes: ["email", "phone_number"] + UUID as username Yes Yes No Yes

The last option is the most robust if you need both email and phone as sign-in identifiers. The trade-off is that Hosted UI will render a username field (since the pool is in alias mode), so you need a custom sign-up UI to pass the UUID programmatically and keep the experience clean for the user.

Note: The AliasAttributes + manual UUID approach is only possible via the API or SDK. There is no way to configure this cleanly through the AWS Console alone.


One more thing: how USERNAME is resolved at sign-in

Regardless of which mode your pool uses, InitiateAuth always accepts input through a field named USERNAME. What happens after you pass that value is where the two modes diverge.

In UsernameAttributes mode, Cognito does a straight attribute scan, it looks for a user whose stored email or phone_number attribute matches the string you passed, then maps it to the internal UUID username before proceeding with auth.

USERNAME: "john@example.com"
  → scan users where attribute email = "john@example.com"
  → found → resolve to internal UUID
  → proceed with auth

In AliasAttributes mode, Cognito resolves in two steps:

  1. Exact match on the internal username field first.
  2. If no match, fall through to an alias attribute scan.
USERNAME: "a1b2c3d4-uuid"
  → exact match on username → proceed with auth

USERNAME: "john@example.com"
  → no exact username match
  → scan alias attributes where email = "john@example.com"
  → found → proceed with auth

This two-step resolution is exactly why the AliasAttributes + manual UUID pattern works so cleanly, sign-in with UUID hits step 1 immediately, sign-in with email or phone falls through to step 2. Both paths work with no ambiguity.

UsernameAttributes mode AliasAttributes mode
Lookup strategy Attribute scan only Exact username match first, then alias scan
Arbitrary string as USERNAME Fails - no attribute match Works if it matches the stored username
Email / phone as USERNAME Works Works if declared as alias

Quick test for your configuration: Open the Cognito console, navigate to your User Pool, and check under Sign-in experience → Sign-in identifiers. If you see "Email" and/or "Phone number" listed without "Username", you're in UsernameAttributes mode. If "Username" is listed, you're in alias mode.


The pool configuration you pick at creation time is permanent, it cannot be changed after the pool exists. Spend the five extra minutes getting this right on day one.

A

About Ankush Jain

Hi, I am Ankush Jain - a software engineer with 13+ years of experience building scalable software systems across backend, frontend, and cloud platforms.

WebsiteTwitterGitHubLinkedIn
CoderJony

Full-stack engineering, cloud architecture, and scalable systems

ankushjain358@gmail.com
© 2026 CoderJony. All rights reserved.