aws-amplify / amplify-flutter

A declarative library with an easy-to-use interface for building Flutter applications on AWS.
https://docs.amplify.aws
Apache License 2.0
1.32k stars 248 forks source link

Exception: There is no active TOTP setup #4490

Closed dkliss closed 7 months ago

dkliss commented 8 months ago

Description

-EDIT, Correction on sync

Hi,

When setting up TOTP, after Amplify.Auth.setUpTotp() has been setup, we will be at Amplify.Auth.verifyTotpSetup(totpCode). The verifyTotpSetup() only allows single attempt to enter correct code. And if second attempt is made I get below exception. Is this how it is expected to work (i.e. only one attempt)?

flutter: Error confirming attribute update: AuthPreconditionException {
  "message": "There is no active TOTP setup",
  "recoverySuggestion": "Call Amplify.Auth.setUpTotp first"
}
amplify_flutter: ^1.6.1
amplify_auth_cognito: ^1.6.2

To reproduce:

  1. run Amplify.Auth.setUpTotp()
  2. Connect with Authenticator.
  3. Run Amplify.Auth.verifyTotpSetup(totpCode) with incorrect code --> Exception: "Code mismatch"
  4. Run Amplify.Auth.verifyTotpSetup(totpCode) with correct code--> Exception: "There is no active TOTP setup."
  5. Re-run Amplify.Auth.setUpTotp(), which will create new code with different sync secret.
  6. Re-scan new qr code with Authenticator.
  7. Re-Run Amplify.Auth.verifyTotpSetup(totpCode) with correct code -> *This function is void and no success response is received --> No exception means success.

In above scenario, even if I do not remove (i.e. execute step 6) authenticator scanned in step 2 above (Connect with Authenticator), Amplify.Auth.verifyTotpSetup() still works even after a new Amplify.Auth.setUpTotp() is generated in step 5.

To reproduce (Step 6 removed from above scenario):

  1. run Amplify.Auth.setUpTotp()
  2. Connect with Authenticator.
  3. Run Amplify.Auth.verifyTotpSetup(totpCode) with incorrect code --> Exception: "Code mismatch"
  4. Run Amplify.Auth.verifyTotpSetup(totpCode) with correct code--> Exception: "There is no active TOTP setup."
  5. Re-run Amplify.Auth.setUpTotp(), which will create new code with different sync secret. 6 Re-Run Amplify.Auth.verifyTotpSetup(totpCode) with correct code -> *This function is void and no success response is received --> No exception means success.

Whats happening in this seems like old QR code is still in Amplify and hence when I add verify code, that one is used.

Categories

Steps to Reproduce

No response

Screenshots

No response

Platforms

Flutter Version

3.19.1

Amplify Flutter Version

1.6.1

Deployment Method

Amplify CLI

Schema

No response

Equartey commented 8 months ago

Hi @dkliss, good question, the observed behavior is expected when using Amplify.Auth.verifyTotpSetup().

The correct API to call while onboarding a User to MFA is Amplify.Auth.confirmSignIn(confirmationValue: totpCode).

If the code is wrong, a CodeMismatchException will be thrown allowing you to try again with no need to call verifyTotpSetup(). After a valid code, the user will be signed in.

We have a command line example app with this implementation found on our github.

dkliss commented 8 months ago

Hi @dkliss, good question, the observed behavior is expected when using Amplify.Auth.verifyTotpSetup().

The correct API to call while onboarding a User to MFA is Amplify.Auth.confirmSignIn(confirmationValue: totpCode).

If the code is wrong, a CodeMismatchException will be thrown allowing you to try again with no need to call verifyTotpSetup(). After a valid code, the user will be signed in.

We have a command line example app with this implementation found on our github.

Thanks @Equartey for confirming this is as expected.

As a user when I go through the QR setup process, what is happening is, if i enter incorrect code for Amplify.Auth.verifyTotpSetup(totpCode), then I cannot retry after ONE attempt. The only way i can re-verify is to re-generate QR code, re-do scan and then re-run verify code & that from user perspective seems a lot.

What I was expecting is that a user have option to EITHER retry verify OR if only verifyTotpSetup(totpCode) is expected and setUpTotp() has been run, a user is always taken to verifyTotpSetup(totpCode) and not asked to re-run setUpTotp() and repeat all process (including re-scan with Authenticator).

I am not adding TOTP at Sign In. But allowing user to update/enable TOTP ONLY after a user has Signed In i.e. this API Amplify.Auth.confirmSignIn(confirmationValue) is not available to me unless I log out and login again after setUpTotp() and verifyTotpSetup(totpCode) are complete.

The Amplify.Auth.confirmSignIn(confirmationValue) works as expected i.e. it does allow multiple attempts. I only have issue with verifyTotpSetup(totpCode) , which restrict a user to single attempt else re-do all scan-sync process.

If a user see verifyTotpSetup() with an exception, it may be perceived as TOTP not setup and user will need to repeat process esp if verifyTotpSetup() is mandatory Or is this step Optional?

Equartey commented 8 months ago

Hi @dkliss, I looked into it further and it does actually appear to be a bug. Thank you for the extra context with your use case as it helped narrow down the issue.

I'm working on addressing it and will update you here when I can.

dkliss commented 8 months ago

Hi @dkliss, I looked into it further and it does actually appear to be a bug. Thank you for the extra context with your use case as it helped narrow down the issue.

I'm working on addressing it and will update you here when I can.

Thanks @Equartey. I will wait for update to retest.

Equartey commented 7 months ago

Hi @dkliss this has been fixed in amplify_auth_cognito: ^1.7.1.

I'm going to close this issue as complete, please reach out if you encounter another issue.

dkliss commented 7 months ago

Hi @Equartey, thanks for the update.

I tried below but I received no exception such as "Code Mismatch" when I enter any incorrect code.

With correct code, TOTP is setup as expected.

amplify_flutter: ^1.7.0
amplify_auth_cognito: ^1.7.1
Flutter 3.19.3 
Test Platform: Android & iOS Emulators.

What I did:

  1. Update from 1.6.1 to newer packages above.
  2. Disabled old TOTP and removed app from authenticator.
  3. Run setTOTP and then verifyTOTP with incorrect code --> No code mismatch exception raised. Expected something like CodeMismatchException" exception. I did not received EnableSoftwareTokenMfaException" either. FAIL
  4. Removed authenticator client from mobile.
  5. Re-run steps 1 and 2 and 3 and this time with correct code. TOTP setup as expected. PASS.
Equartey commented 7 months ago

Hi @dkliss, we catch the exception internally, but allow multiple calls to verifyTotpSetup without the need to restart setup.

I suspect the problem you're facing is not receiving enough feedback during the code verification process. Basically, how would you determine if the attempt was successful or a failure requiring a retry. Which is understandably frustrating.

Can you confirm you're able to submit multiple calls to verifyTotpSetup when passing an incorrect code without having to restart setup (your steps 1-3)?

Try:

  1. call Amplify.Auth.setUpTotp()
  2. call Amplify.Auth.verifyTotpSetup(INVALID_CODE) (can be repeated) -- note: no exception thrown
  3. call Amplify.Auth.verifyTotpSetup(VALID_CODE)
  4. Successful setup

Meanwhile, we will investigate how to provide better feedback to you.

Equartey commented 7 months ago

Hi @dkliss, I have a work around for you in the meantime.

When setting up a user's TOTP for the first time, you can check the user's MFA preference to see if TOTP is enabled. Now this will only be of value during initial setup on the first device as it only checks to see if TOTP is enabled for the user not, if the current attempt was successful.

final plugin = Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey);
final response = await plugin.fetchMfaPreference();
print(response);
// TOTP not enabled
// UserMfaPreference {
//   "enabled": [],
//   "preferred": null
// }
// TOTP enabled
// UserMfaPreference {
//   "enabled": [
//     "TOTP" // Can check if this exists to determine if setup was successful
//   ],
//   "preferred": "TOTP"
// }

Not a perfect fix, but hopefully unblocks you while we investigate a better solution.

dkliss commented 7 months ago

Hi @dkliss, we catch the exception internally, but allow multiple calls to verifyTotpSetup without the need to restart setup.

I suspect the problem you're facing is not receiving enough feedback during the code verification process. Basically, how would you determine if the attempt was successful or a failure requiring a retry. Which is understandably frustrating.

Can you confirm you're able to submit multiple calls to verifyTotpSetup when passing an incorrect code without having to restart setup (your steps 1-3)?

Try:

  1. call Amplify.Auth.setUpTotp()
  2. call Amplify.Auth.verifyTotpSetup(INVALID_CODE) (can be repeated) -- note: no exception thrown
  3. call Amplify.Auth.verifyTotpSetup(VALID_CODE)
  4. Successful setup

Meanwhile, we will investigate how to provide better feedback to you.

Hi @Equartey , thanks for the response.

I can confirm multiple attempts works as expected. I tried 5 or 6 attempts and did not received "There is no active TOTP setup" issue anymore.

CodeMismatchException will be useful so a user can be redirected correctly based on if a user enters correct or incorrect code.

I will wait for this update.

dkliss commented 7 months ago

Hi @dkliss, I have a work around for you in the meantime.

When setting up a user's TOTP for the first time, you can check the user's MFA preference to see if TOTP is enabled. Now this will only be of value during initial setup on the first device as it only checks to see if TOTP is enabled for the user not, if the current attempt was successful.

final plugin = Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey);
final response = await plugin.fetchMfaPreference();
print(response);
// TOTP not enabled
// UserMfaPreference {
//   "enabled": [],
//   "preferred": null
// }
// TOTP enabled
// UserMfaPreference {
//   "enabled": [
//     "TOTP" // Can check if this exists to determine if setup was successful
//   ],
//   "preferred": "TOTP"
// }

Not a perfect fix, but hopefully unblocks you while we investigate a better solution.

Thanks @Equartey for the proposed workaround.

I tested the workaround and I can handle failed entry this way for now for first Authenticator ONLY (as you mentioned).

However, (as you indicated) when I generate a new QR code for second Authenticator, this approach does not work because mfasList received from fetchMfaPreference() already have a TOTP enabled (because of the first Authenticator).

CodeMismatchException per uniquely generated QR code secret is important (I think), else this will not work properly. And this may tie into how do we track all secrets or QRs (i.e. Authenticators) generated & which of those are verified and which of those are not. I.e. fetchMfaPreference() should send a list of all active/inactive TOTPs.

I decided to not use this workaround for now as this does not seem to solve the problem completely and may generate false positives. I will wait for an update on this to retest.

UPDATE: I decided to re-implement the workaround with a (client side) Limitation of one Authenticator allowed per user". I.e. a user will ONLY be able to to setup a maximum of one Authenticator app. This is because, single Authenticator setup and verify (with proposed workaround) works as expected.

Once an update is received on how to handle multiple separate Authenticators to verifyTotp as well as enable/disable, I will retest that.

Equartey commented 7 months ago

Hi @dkliss, I appreciate the discussion so far. It's been helpful for me to understand your use case. I've opened https://github.com/aws-amplify/amplify-flutter/pull/4558 to throw an exception during TOTP setup. Will update you when it's released.

I believe your updated approach, limiting users to one Authenticator device, improves the user experience until we can handle multiple TOTP secrets properly. Which we will continue to track in the other issue you've opened, https://github.com/aws-amplify/amplify-flutter/issues/4509.

Equartey commented 7 months ago

Hi @dkliss, we ended up reverting 1.7.1 as it broke existing behavior, but we have released the idle fix. Please consume amplify_auth_cognito: ^1.7.2.

Amplify.Auth.verifyTotpSetup(INVALID_CODE) now throws an EnableSoftwareTokenMfaException and allows retries without restarting the setup process.

Again, thank you for bringing this to our attention. I'm going to close this, but please reach out if you have any issues.

dkliss commented 7 months ago

Hi @Equartey Thanks you for response.

I verified this and I received exception as expected. Much appreciate your help on this throughout.