amplify-android: After remembering device, calling Amplify.Auth.signOut() causes next signIn() call to return CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE

Before opening, please confirm:

Language and Async Model

Kotlin - Coroutines

Amplify Categories

Authentication

Gradle script dependencies

// From TOML file
[versions]
amplify = "2.2.1"
androidGradlePlugin = "7.4.1"
kotlin = "1.8.0"
kotlin-coroutines = "1.6.4"

[libraries]
amplify-cognito = { module = "com.amplifyframework:aws-auth-cognito", version.ref = "amplify" }
amplify-kotlin = { module = "com.amplifyframework:core-kotlin", version.ref = "amplify" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlin-coroutines" }

Environment information

------------------------------------------------------------
Gradle 7.6
------------------------------------------------------------

Build time:   2022-11-25 13:35:10 UTC
Revision:     daece9dbc5b79370cc8e4fd6fe4b2cd400e150a8

Kotlin:       1.7.10
Groovy:       3.0.13
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          11.0.17 (Homebrew 11.0.17+0)
OS:           Mac OS X 13.2.1 aarch64

Describe the bug

If Amplify.Auth.rememberDevice() is called after confirmSignIn(), then I would expect that the device would be remembered and the user would not be challenged for MFA if they were to sign out and sign back in.

Currently, if a device is set to be remembered after a user confirms signin and then they sign out and sign in again, we receive a CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE as the signIn() call’s nextStep.signInStep. Unless I’m misunderstanding the expected outcome of a signOut() call, I think the user should be receiving DONE as the nextStep.signInStep

Reproduction steps (if applicable)

  1. Call Amplify.Auth.signIn(username, password). Result should return CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE as next step
  2. Call Amplify.Auth.confirmSignIn(code). Result should return DONE as next step
  3. Call Amplify.Auth.rememberDevice()
  4. Call Amplify.Auth.signOut(options = AuthSignOutOptions.builder().globalSignOut(false).build())
  5. Call Amplify.Auth.signIn(username, password). Result returns CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE again as next step. Result should have returned DONE 🐛

amplifyconfiguration.json

{
  "auth": {
    "plugins": {
      "awsCognitoAuthPlugin": {
        "IdentityManager": {
          "Default": {}
        },
        "CognitoUserPool": {
          "Default": {
            "PoolId": "REDACTED",
            "AppClientId": "REDACTED",
            "Region": "us-east-1"
          }
        },
        "Auth": {
          "Default": {
            "authenticationFlowType": "USER_SRP_AUTH",
            "OAuth": {
              "WebDomain": "REDACTED",
              "AppClientId": "REDACTED",
              "SignInRedirectURI": "REDACTED",
              "SignOutRedirectURI": "REDACTED",
              "Scopes": [
                "aws.cognito.signin.user.admin"
              ]
            }
          }
        }
      }
    }
  }
}

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 28 (9 by maintainers)

Most upvoted comments

@nautilux2 You can try to use the UDID of the user on subsequent logins to ensure the AWS SDK is looking up the device key using the right kind of username.

  • After the first login, store the AWS username UDID (Amplify.Auth.getCurrentUser().username) in sharedprefs (or some alternative local storage) mapped to the user’s entered username.
  • And then on future logins, get the matching AWS UDID username to the user’s entered username from sharedprefs when calling Amplify.Auth.signin() so that when SRPCognitoActions.initiateSRPAuthAction() is called in the SDK, it will find the device key because it’s now using the username that was used to store the device key.

@gpanshu why was this ticket closed? This is still an active bug.

@TylerMcCraw Thanks for the deep dive. 🙌

@tylerjroach @div5yesh After spending a considerable amount of time in the debugger, I believe I’ve tracked down the cause of this issue.

Particularly, at this line of code. The username in the initial signin event is the same as the username that’s passed to Amplify.Auth.signin (in our case, an email address from the signin screen’s “Username” input field). When SRPCognitoActions.initiateSRPAuthAction() is eventually called, the username (still an email address, in our case) is used to look up the device metadata (and more importantly, the device key). This device metadata is used to determine if the user ought to be challenged for MFA. If the device key is null, they will likely be challenged. And in our case, it’s always null. The reason the device key is always null is because at this line the USERNAME parameter returned is a UUID string (e.g. “7n1h6e06-7503-4b21-80ad-d52ae3769dv8”) and NOT the email address from the original signin call. This UUID string username is then used for all credential store calls going forward, including the one that is used to store the device key after the user confirms with their 2FA code and rememberDevice() is called.

So, the lookup from EncryptedSharedPreferences via AuthEnvironment.getDeviceMetadata(username) to get the device key always uses the username passed in the Amplify.Auth.signin call (e.g. user@company.com) And the credentialStoreClient.storeCredentials call via SignInCognitoActions.confirmDevice to store the device key always uses the AWS username UUID string (e.g. “7n1h6e06-7503-4b21-80ad-d52ae3769dv8”).

To sum it up, looking up the device key with “user@company.com” returns null because the device key is stored with “7n1h6e06-7503-4b21-80ad-d52ae3769dv8”, therefore Amplify can’t fetch the device key and therefore Amplify responds with a challenge step every time requiring the user to enter their 2FA code.

Possible Solution

If amplify-android were to use the AWS username UUID to look up the device metadata here instead of the username that’s passed along from the Amplify.Auth.signin() call, then this step would likely be able to get the device key and then the user wouldn’t be challenged again for the 2FA code. I would open up a PR for this fix, but I can’t think of a way to get the AWS username UUID string from the credentials that were passed in the original signin call.

@TylerMcCraw Thanks for the ping. I know Divyesh has his own sample app that he was attempting to replicate this. We can investigate the sample provided to see if there are any differences that stand out.

In the meantime, the easiest way to investigate this issue may still be to provide the logs that @div5yesh requested so we can see exactly what is happening on your devices. It’s possible the issue could exist in the service configuration and we may still not be able to easily replicate with the sample.