fix(auth): key device metadata by inputUsername so alias sign-in pools remember devices - #4254
Merged
harsh62 merged 5 commits intoAug 4, 2026
Conversation
…n-in `ConfirmDevice` stores device metadata under `signedInData.inputUsername` (the username the caller signed in with), but the sign-in flow read it back using `parameters["USERNAME"]` — the value Cognito echoes, which is the sub for pools configured with alias sign-in. On those pools the read never matched the write, so `DEVICE_KEY` was omitted from the password-verifier response. Cognito treated every sign-in as a new device: it returned fresh `NewDeviceMetadata`, `ConfirmDevice` stored it under the email again, and the next sign-in repeated the cycle — MFA on every sign-in and a new device record per attempt (one user had accumulated 50). Pools where the username *is* the email are unaffected, since the two values coincide there. Reads now use the same value as the write: - VerifyPasswordSRP: `inputUsername` - VerifySignInChallenge (both call sites): `challenge.inputUsername ?? username` Nothing sent to Cognito changes — `username` still supplies `USERNAME` on every request; only the keychain lookups moved. Known gap: the DeviceSRP actions (InitiateAuthDeviceSRP, VerifyDevicePasswordSRP) still look up by the echoed username. That path only executes once Cognito starts accepting the device key, so it is now reachable and tracked separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Commit 122689e changed the sign-in flow to read device metadata with the caller's `inputUsername` (matching how `ConfirmDevice` writes it), but left the `deviceNotFound` recovery paths deleting under `username` — the value Cognito echoes, which is the sub for pools with alias sign-in. When a stored device key is stale, Cognito rejects the password verifier with `ResourceNotFoundException`. The recovery path then deletes the wrong keychain entry, leaving the real one in place, so the retry re-reads the same stale key, Cognito rejects it again, and sign-in loops indefinitely. Read and delete now use the same value in both actions: - VerifyPasswordSRP: `inputUsername` - VerifySignInChallenge: `challenge.inputUsername ?? username` Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Once a device is remembered, Cognito answers the password verifier with DEVICE_SRP_AUTH instead of an MFA challenge, routing sign-in through InitiateAuthDeviceSRP / VerifyDevicePasswordSRP. Both looked the stored device metadata up by the Cognito-echoed username (the sub, on pools with alias sign-in) rather than the `inputUsername` it was written under, so the lookup missed and the request omitted DEVICE_KEY — Cognito then rejected sign-in with "Missing required parameter DEVICE_KEY". - UserPoolSignInHelper: carry `inputUsername ?? username` into `.initiateDeviceSRP` so InitiateAuthDeviceSRP reads (and re-sends) under the right key. - VerifyDevicePasswordSRP: read metadata with `inputUsername`; the request still sends `username` (the echoed value), which is what Cognito expects. Completes the alias-pool device-remembering fix (see prior two commits); the DEVICE_SRP_AUTH path is only reachable once DEVICE_KEY is accepted, so it could not be exercised until the earlier read/delete fixes landed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
@thisisabhash This fixes a bug in production. Would it be best for me to submit a bug report as well? Our iOS users devices are never saved, leading to MFA every time. |
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
harsh62
had a problem deploying
to
IntegrationTest
August 4, 2026 14:40 — with
GitHub Actions
Failure
Contributor
|
All checks passed on #4263. |
harsh62
approved these changes
Aug 4, 2026
This was referenced Aug 5, 2026
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.
Issue
Related to #4196, which fixed the device-metadata keychain key for the write path (
ConfirmDevice) but left several read paths keyed off a different value.Description
On a user pool configured with alias sign-in (e.g. email alias),
RespondToAuthChallengereturns aUSERNAMEchallenge parameter equal to the user'ssub, not the value the caller signed in with. #4196 madeConfirmDevicestore device metadata undersignedInData.inputUsername(the caller-supplied username), but the sign-in actions still read that metadata back using the echoedUSERNAME(the sub). The read key never matched the write key, so:DEVICE_KEYwas omitted from the password-verifier response, and Cognito treated every sign-in as a new device — MFA on every sign-in, and a new device record accumulating per attempt.This PR aligns every device-metadata read (and the two
deviceNotFounddeletes) with theinputUsernameused by the write, across the SRP, challenge, and device-SRP flows:VerifyPasswordSRP— read and thedeviceNotFounddelete now useinputUsername. (The delete previously used the echoedusername; when a stored device key was stale, Cognito returnedResourceNotFoundException, the recovery path deleted the wrong keychain entry, and the retry re-read the same stale key and looped indefinitely.)VerifySignInChallenge— both reads and thedeviceNotFounddelete now usechallenge.inputUsername ?? username.UserPoolSignInHelper— carriesinputUsername ?? usernameinto.initiateDeviceSRP, soInitiateAuthDeviceSRPlooks the metadata up (and re-sendsDEVICE_KEY) under the right key. Without this the device-SRP flow fails withMissing required parameter DEVICE_KEY.VerifyDevicePasswordSRP— reads metadata withinputUsername; the outgoing request still sendsusername(the echoed value), which is what Cognito expects.Nothing sent to Cognito changes semantically —
usernamestill supplies the requestUSERNAMEon every request; only the keychain lookups moved to the value the metadata was written under.Verification
Manually verified end-to-end on a real device against an alias-sign-in pool (
us-east-1email alias, MFA via email OTP):rememberDevice→ subsequent sign-in takesDEVICE_SRP_AUTHand completes with no MFA.deviceNotFoundrecovery + retry, then a normal MFA challenge (no infinite loop).General Checklist
Given When Theninline code documentation and are named accordinglytestThing_condition_expectation()By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.