Name:
interface
Value:
Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated Nov 4, 2024

Multi-step sign-in

After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi step processes. The required steps are determined by the configuration you provided when you define your auth resources like described on Manage MFA Settings page.

Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the nextStep parameter in the signin result.

New enumeration values

When Amplify adds a new enumeration value (e.g., a new enum class entry or sealed class subtype in Kotlin, or a new enum value in Swift/Dart/Kotlin), it will publish a new minor version of the Amplify Library. Plugins that switch over enumeration values should include default handlers (an else branch in Kotlin or a default statement in Swift/Dart/Kotlin) to ensure that they are not impacted by new enumeration values.

The Amplify.Auth.signIn API returns a SignInResult object which indicates whether the sign-in flow is complete or whether additional steps are required before the user is signed in.

To see if additional signin steps are required, inspect the sign in result's nextStep.signInStep property.

  • If the sign-in step is done, the flow is complete and the user is signed in.
  • If the sign-in step is not done, one or more additional steps are required. These are explained in detail below.

The signInStep property is an enum of type AuthSignInStep. Depending on its value, your code should take one of the actions mentioned on this page.

Future<SignInResult> signInWithCognito(
String username,
String password,
) async {
final SignInResult result = await Amplify.Auth.signIn(
username: username,
password: password,
);
return _handleSignInResult(result);
}
Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
case AuthSignInStep.continueSignInWithMfaSelection:
// Handle select from MFA methods case
case AuthSignInStep.continueSignInWithMfaSetupSelection:
// Handle select from MFA methods available to setup
case AuthSignInStep.continueSignInWithEmailMfaSetup:
// Handle email setup case
case AuthSignInStep.confirmSignInWithOtpCode:
// Handle email MFA case
case AuthSignInStep.continueSignInWithTotpSetup:
// Handle TOTP setup case
case AuthSignInStep.confirmSignInWithTotpMfaCode:
// Handle TOTP MFA case
case AuthSignInStep.confirmSignInWithSmsMfaCode:
// Handle SMS MFA case
case AuthSignInStep.confirmSignInWithNewPassword:
// Handle new password case
case AuthSignInStep.confirmSignInWithCustomChallenge:
// Handle custom challenge case
case AuthSignInStep.resetPassword:
// Handle reset password case
case AuthSignInStep.confirmSignUp:
// Handle confirm sign up case
case AuthSignInStep.done:
safePrint('Sign in is complete');
}
}

Confirm sign-in with SMS MFA

If the next step is confirmSignInWithSmsMfaCode, Amplify Auth has sent the user a random code over SMS and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the confirmSignIn API.

The result includes an AuthCodeDeliveryDetails member. It includes additional information about the code delivery, such as the partial phone number of the SMS recipient, which can be used to prompt the user on where to look for the code.

Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
case AuthSignInStep.confirmSignInWithSmsMfaCode:
final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!;
_handleCodeDelivery(codeDeliveryDetails);
// ...
}
}
void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) {
safePrint(
'A confirmation code has been sent to ${codeDeliveryDetails.destination}. '
'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.',
);
}
Future<void> confirmMfaUser(String mfaCode) async {
try {
final result = await Amplify.Auth.confirmSignIn(
confirmationValue: mfaCode,
);
return _handleSignInResult(result);
} on AuthException catch (e) {
safePrint('Error confirming MFA code: ${e.message}');
}
}

Confirm sign-in with TOTP MFA

If the next step is confirmSignInWithTOTPCode, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires.

After the user enters the code, your implementation must pass the value to Amplify Auth confirmSignIn API.

Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
// ···
case AuthSignInStep.confirmSignInWithTotpMfaCode:
safePrint('Enter a one-time code from your registered authenticator app');
// ···
}
}
// Then, pass the TOTP code to `confirmSignIn`
Future<void> confirmTotpUser(String totpCode) async {
try {
final result = await Amplify.Auth.confirmSignIn(
confirmationValue: totpCode,
);
return _handleSignInResult(result);
} on AuthException catch (e) {
safePrint('Error confirming TOTP code: ${e.message}');
}
}

Confirm sign-in with Email MFA

If the next step is confirmSignInWithOtpCode, Amplify Auth has sent the user a random code to their email address and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the confirmSignIn API.

The result includes an AuthCodeDeliveryDetails member. It includes additional information about the code delivery, such as the partial email address of the recipient, which can be used to prompt the user on where to look for the code.

Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
case AuthSignInStep.confirmSignInWithOtpCode:
final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!;
_handleCodeDelivery(codeDeliveryDetails);
// ...
}
}
void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) {
safePrint(
'A confirmation code has been sent to ${codeDeliveryDetails.destination}. '
'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.',
);
}
Future<void> confirmMfaUser(String mfaCode) async {
try {
final result = await Amplify.Auth.confirmSignIn(
confirmationValue: mfaCode,
);
return _handleSignInResult(result);
} on AuthException catch (e) {
safePrint('Error confirming MFA code: ${e.message}');
}
}

Continue sign-in with MFA Selection

If the next step is continueSignInWithMFASelection, the user must select the MFA method to use. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using confirmSignIn API.

The MFA types which are currently supported by Amplify Auth are:

  • MfaType.sms
  • MfaType.totp
  • MfaType.email
Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
// ···
case AuthSignInStep.continueSignInWithMfaSelection:
final allowedMfaTypes = result.nextStep.allowedMfaTypes!;
final selection = await _promptUserPreference(allowedMfaTypes);
return _handleMfaSelection(selection);
// ···
}
}
Future<MfaType> _promptUserPreference(Set<MfaType> allowedTypes) async {
// ···
}
Future<void> _handleMfaSelection(MfaType selection) async {
try {
final result = await Amplify.Auth.confirmSignIn(
confirmationValue: selection.confirmationValue,
);
return _handleSignInResult(result);
} on AuthException catch (e) {
safePrint('Error resending code: ${e.message}');
}
}

Continue sign-in with Email Setup

If the next step is continueSignInWithEmailMfaSetup, then the user must provide an email address to complete the sign in process. Once this value has been collected from the user, call the confirmSignIn API to continue.

Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
// ···
case AuthSignInStep.continueSignInWithEmailMfaSetup:
// Prompt user to enter an email address they would like to use for MFA
// ···
}
}
// Then, pass the email address to `confirmSignIn`
Future<void> confirmEmailUser(String emailAddress) async {
try {
final result = await Amplify.Auth.confirmSignIn(
confirmationValue: emailAddress,
);
return _handleSignInResult(result);
} on AuthException catch (e) {
safePrint('Error confirming email address: ${e.message}');
}
}

Continue sign-in with TOTP Setup

If the next step is continueSignInWithTOTPSetup, then the user must provide a TOTP code to complete the sign in process. The step returns an associated value of type TOTPSetupDetails which would be used for generating TOTP. TOTPSetupDetails provides a helper method called getSetupURI that can be used to generate a URI, which can be used by native password managers for TOTP association. For example. if the URI is used on Apple platforms, it will trigger the platform's native password manager to associate TOTP with the account. For more advanced use cases, TOTPSetupDetails also contains the sharedSecret that will be used to either generate a QR code or can be manually entered into an authenticator app.

Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process.

Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
// ···
case AuthSignInStep.continueSignInWithTotpSetup:
final totpSetupDetails = result.nextStep.totpSetupDetails!;
final setupUri = totpSetupDetails.getSetupUri(appName: 'MyApp');
safePrint('Open URI to complete setup: $setupUri');
// ···
}
}
// Then, pass the TOTP code to `confirmSignIn`
Future<void> confirmTotpUser(String totpCode) async {
try {
final result = await Amplify.Auth.confirmSignIn(
confirmationValue: totpCode,
);
return _handleSignInResult(result);
} on AuthException catch (e) {
safePrint('Error confirming TOTP code: ${e.message}');
}
}

Continue sign-in with MFA Setup Selection

If the next step is continueSignInWithMfaSetupSelection, then the user must indicate which of the available MFA methods they would like to setup. After the user selects an MFA method to setup, your implementation must pass the selected MFA method to the confirmSignIn API.

The MFA types which are currently supported by Amplify Auth are:

  • MfaType.sms
  • MfaType.totp
  • MfaType.email
Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
// ···
case AuthSignInStep.continueSignInWithMfaSetupSelection:
final allowedMfaTypes = result.nextStep.allowedMfaTypes!;
final selection = await _promptUserPreference(allowedMfaTypes);
return _handleMfaSelection(selection);
// ···
}
}
Future<MfaType> _promptUserPreference(Set<MfaType> allowedTypes) async {
// ···
}
Future<void> _handleMfaSelection(MfaType selection) async {
try {
final result = await Amplify.Auth.confirmSignIn(
confirmationValue: selection.confirmationValue,
);
return _handleSignInResult(result);
} on AuthException catch (e) {
safePrint('Error selecting MFA method: ${e.message}');
}
}

Confirm sign-in with custom challenge

If the next step is confirmSignInWithCustomChallenge, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the AWS Lambda trigger you configured as part of a custom sign in flow.

For example, your custom challenge Lambda may pass a prompt to the frontend which requires the user to enter a secret code.

Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
// ...
case AuthSignInStep.confirmSignInWithCustomChallenge:
final parameters = result.nextStep.additionalInfo;
final hint = parameters['hint']!;
safePrint(hint); // "Enter the secret code"
// ...
}
}

To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the confirmSignIn API.

Future<void> confirmCustomChallenge(String answer) async {
try {
final result = await Amplify.Auth.confirmSignIn(
confirmationValue: answer,
);
return _handleSignInResult(result);
} on AuthException catch (e) {
safePrint('Error confirming custom challenge: ${e.message}');
}
}

Special Handling on confirmSignIn

If failAuthentication=true is returned by the Lambda, Cognito will invalidate the session of the request. This is represented by a NotAuthorizedException and requires restarting the sign-in flow by calling Amplify.Auth.signIn again.

Confirm sign-in with new password

If the next step is confirmSignInWithNewPassword, Amplify Auth requires the user choose a new password they proceeding with the sign in.

Prompt the user for a new password and pass it to the confirmSignIn API.

Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
// ...
case AuthSignInStep.confirmSignInWithNewPassword:
safePrint('Please enter a new password');
// ...
}
}
Future<void> confirmNewPassword(String newPassword) async {
try {
final result = await Amplify.Auth.confirmSignIn(
confirmationValue: newPassword,
);
return _handleSignInResult(result);
} on AuthException catch (e) {
safePrint('Error confirming new password: ${e.message}');
}
}

Reset password

If the next step is resetPassword, Amplify Auth requires that the user reset their password before proceeding. Use the resetPassword API to guide the user through resetting their password, then call Amplify.Auth.signIn when that's complete to restart the sign-in flow.

See the reset password docs for more information.

Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
// ...
case AuthSignInStep.resetPassword:
final resetResult = await Amplify.Auth.resetPassword(
username: username,
);
await _handleResetPasswordResult(resetResult);
// ...
}
}
Future<void> _handleResetPasswordResult(ResetPasswordResult result) async {
switch (result.nextStep.updateStep) {
case AuthResetPasswordStep.confirmResetPasswordWithCode:
final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!;
_handleCodeDelivery(codeDeliveryDetails);
case AuthResetPasswordStep.done:
safePrint('Successfully reset password');
}
}
void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) {
safePrint(
'A confirmation code has been sent to ${codeDeliveryDetails.destination}. '
'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.',
);
}

Confirm Signup

If the next step is resetPassword, Amplify Auth requires that the user confirm their email or phone number before proceeding. Use the resendSignUpCode API to send a new sign up code to the registered email or phone number, followed by confirmSignUp to complete the sign up.

See the confirm sign up docs for more information.

The result includes an AuthCodeDeliveryDetails member. It includes additional information about the code delivery, such as the partial phone number of the SMS recipient, which can be used to prompt the user on where to look for the code.

Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
// ...
case AuthSignInStep.confirmSignUp:
// Resend the sign up code to the registered device.
final resendResult = await Amplify.Auth.resendSignUpCode(
username: username,
);
_handleCodeDelivery(resendResult.codeDeliveryDetails);
// ...
}
}
void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) {
safePrint(
'A confirmation code has been sent to ${codeDeliveryDetails.destination}. '
'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.',
);
}
Future<void> confirmSignUp({
required String username,
required String confirmationCode,
}) async {
try {
await Amplify.Auth.confirmSignUp(
username: username,
confirmationCode: confirmationCode,
);
} on AuthException catch (e) {
safePrint('Error confirming sign up: ${e.message}');
}
}

Once the sign up is confirmed, call Amplify.Auth.signIn again to restart the sign-in flow.

Done

The sign-in flow is complete when the next step is done, which means the user is successfully authenticated. As a convenience, the SignInResult also provides the isSignedIn property, which will be true if the next step is done.

Future<void> _handleSignInResult(SignInResult result) async {
switch (result.nextStep.signInStep) {
// ...
case AuthSignInStep.done:
// Could also check that `result.isSignedIn` is `true`
safePrint('Sign in is complete');
}
}