fix(auth): Consistent device metadata storage key across auth flows#3288
Draft
harsh62 wants to merge 10 commits into
Draft
fix(auth): Consistent device metadata storage key across auth flows#3288harsh62 wants to merge 10 commits into
harsh62 wants to merge 10 commits into
Conversation
The device metadata storage key was derived from the JWT username claim (via AuthHelper.getActiveUsername), which differs between auth flows: USER_PASSWORD_AUTH returns the internal sub-style UUID while USER_SRP_AUTH returns the user alias (email, phone, etc). This caused device metadata stored during one flow to be unretrievable during another, resulting in duplicate device IDs on every login when switching between auth flows. Thread the original user-typed username (inputUsername) through SignedInData, AuthChallenge, SRPEvent, and the evaluateNextStep helper so that ConfirmDevice always stores device metadata under the same key that will be used for retrieval on subsequent logins. The inputUsername field is optional with null default on all data classes to maintain backwards compatibility with existing persisted data (kotlinx.serialization handles missing fields gracefully). Includes unit tests for serialization backwards compatibility, inputUsername threading through evaluateNextStep, and cross-flow integration tests.
Explicitly cast null to String? to disambiguate between the primary constructor (IdToken?, AccessToken?, RefreshToken?, Long?) and the secondary string constructor (String?, String?, String?, Long?).
Add inputUsername to signedInData and authChallenge blocks in all feature test JSON state fixtures and the AuthStateJsonGenerator to match the new runtime behavior. Also fix CognitoUserPoolTokens overload ambiguity in unit tests by using explicit null as String? casts.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3288 +/- ##
==========================================
+ Coverage 55.76% 55.91% +0.15%
==========================================
Files 1082 1084 +2
Lines 31913 32537 +624
Branches 4760 4763 +3
==========================================
+ Hits 17795 18194 +399
- Misses 12266 12488 +222
- Partials 1852 1855 +3 🚀 New features to boost your workflow:
|
- Remove unused imports in DeviceKeyPersistenceInstrumentationTest - Wrap CognitoUserPoolTokens constructor args across lines (max-line-length) - Wrap AuthChallenge constructor args in SignInChallengeHelper (argument-list-wrapping)
Cognito normalizes usernames to lowercase in JWT claims and challenge parameters, but the user-typed input preserves original casing. This caused a storage key mismatch when storing device metadata with inputUsername (mixed case) and retrieving with the JWT username (lowercase). Lowercase the username in all three device metadata storage operations (save, retrieve, delete) in AWSCognitoAuthCredentialStore.
MigrateAuthCognitoActions (USER_PASSWORD_AUTH flow) was not retrieving or sending the stored device key in the InitiateAuth request. This caused Cognito to issue a new device on every USER_PASSWORD_AUTH sign-in, even when a confirmed device already existed from a prior USER_SRP_AUTH sign-in. Add device metadata retrieval and DEVICE_KEY parameter injection to MigrateAuthCognitoActions, matching the existing pattern in SRPCognitoActions. Also update integration tests to verify device key stays consistent when alternating between USER_PASSWORD_AUTH and USER_SRP_AUTH flows.
harsh62
commented
Apr 22, 2026
Comment on lines
+85
to
+91
| /** | ||
| * Sign in with USER_PASSWORD_AUTH, remember device (1 device). | ||
| * Sign out, sign in with USER_SRP_AUTH — device count should stay 1, same device key. | ||
| * Sign out, sign in with USER_PASSWORD_AUTH again — still 1, same key. | ||
| * Sign out, sign in with USER_SRP_AUTH again — still 1, same key. | ||
| */ | ||
| @Test |
Member
Author
There was a problem hiding this comment.
@mattcreaser the test steps that are failing on main.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes device key persistence when switching between authentication flows (USER_PASSWORD_AUTH and USER_SRP_AUTH). Three root causes identified and fixed:
1. Device metadata storage key mismatch across auth flows
The device metadata storage key was derived from the JWT
usernameclaim (viaAuthHelper.getActiveUsername()), which can differ betweenUSER_PASSWORD_AUTH(returns internal UUID) andUSER_SRP_AUTH(returns user alias like email). This caused device key lookup failures on subsequent logins when auth flows change, generating duplicate device IDs.Fix: Thread the original user-typed username (
inputUsername) throughSignedInData,AuthChallenge,SRPEvent, and theevaluateNextStephelper chain soConfirmDevicestores device metadata under the same key used for retrieval.2. Case-sensitive storage key mismatch
Cognito normalizes usernames to lowercase in JWT claims and challenge parameters, but the user-typed input preserves original casing. This caused a storage key mismatch when storing with
inputUsername(mixed case) and retrieving with the JWT username (lowercase).Fix: Lowercase the username in all three device metadata storage operations (save, retrieve, delete) in
AWSCognitoAuthCredentialStore.3. USER_PASSWORD_AUTH flow missing DEVICE_KEY in InitiateAuth request
MigrateAuthCognitoActions(USER_PASSWORD_AUTH flow) never retrieved stored device metadata or sentDEVICE_KEYin theInitiateAuthrequest. This caused Cognito to issue a new device on every USER_PASSWORD_AUTH sign-in, even when a confirmed device already existed from a prior sign-in.SRPCognitoActionsalready had this logic.Fix: Add
getDeviceMetadata()retrieval andDEVICE_KEYparameter injection toMigrateAuthCognitoActions, matching the existing pattern inSRPCognitoActions.Changes
Data classes (3 files): Added
inputUsername: String? = nulltoSignedInData,AuthChallenge, andSRPEvent.RespondPasswordVerifier/RetryRespondPasswordVerifier. All optional with null default for backwards-compatible serialization.Helper (1 file):
SignInChallengeHelper.evaluateNextStepaccepts and propagatesinputUsername.Actions (8 files): All callers of
evaluateNextSteppass the originalevent.usernameasinputUsername.ConfirmDeviceusesinputUsername ?: usernameas the storage key.MigrateAuthCognitoActionsnow retrieves and sendsDEVICE_KEY.Credential store (1 file):
AWSCognitoAuthCredentialStorelowercases username in device metadata key generation.State machine (2 files):
SRPActionsinterface andSRPSignInStateresolver passinputUsernamethrough SRP event chain.Test plan
deviceKey_stays_consistent_across_alternating_auth_flows— FAILS on main (expected 1 device, got 2), PASSES on this branchdeviceKey_persists_across_same_flow_signIn_signOut— FAILS on main (expected 1, got 3), PASSES on this branchrememberDevice_succeeds_after_signIn_and_signOut— PASSES (no regression)Addresses #3301