Sign in with SMS by using Amplify Flutter

Muhammed Salih Guler
Flutter Community
Published in
5 min readJun 30, 2022

So far, you were able to set up your Flutter project with AWS Amplify and create a sign up flow. Now it is time to learn how you can sign users in to your application.

In the span of this blog post series, you will be learning:

Sign in users with phone number and phone number confirmation (MFA)

Enable MFA for the project

MFA cannot be unconditionally enabled for all users after creating a user pool. If you want to enable MFA for your ongoing project, it will be marked as “Optional” for users. In this mode, MFA must be enabled on a user-by-user basis, either through an Admin SDK (e.g. via a Lambda trigger as part of the sign-up process), or manually in the Cognito console.

If you’d like to make MFA required for users, you must first delete your auth resource by running amplify remove auth, then re add the auth.

You will learn how to remove and re-add the auth to enable MFA for all users at this step:

? amplify remove auth
? Choose the resource you would want to remove amplifysmstest
? Are you sure you want to delete the resource? This action deletes all files rela
ted to this resource from the backend directory. Yes
✅ Successfully removed resource

Once it is removed, re add the auth again like you did before:

? amplify add auth
? Using service: Cognito, provided by: awscloudformation

The current configured provider is Amazon Cognito.

? Do you want to use the default authentication and security configuration? Manual
configuration
? Select the authentication/authorization services that you want to use: User Sign-
Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage features fo
r images or other content, Analytics, and more)
? Provide a friendly name for your resource that will be used to label this categor
y in the project: (Either click Enter or write a name)
? Enter a name for your identity pool. (Either click Enter or write a name)
? Allow unauthenticated logins? (Provides scoped down permissions that you can cont
rol via AWS IAM) No
? Do you want to enable 3rd party authentication providers in your identity pool? No
? Provide a name for your user pool: (Either click Enter or write a name)
Warning: you will not be able to edit these selections.
? How do you want users to be able to sign in? Phone Number
? Do you want to add User Pool Groups? No
? Do you want to add an admin queries API? No
? Multifactor authentication (MFA) user login options:
OFF
❯ ON (Required for all logins, can not be enabled later)
OPTIONAL (Individual users can use MFA)
I want to learn more.
? For user login, select the MFA types: (Press <space> to select, <a> to toggle all
, <i> to invert selection)
❯◉ SMS Text Message
◯ Time-Based One-Time Password (TOTP)
? Specify an SMS authentication message: Your authentication code is {####}
? Email based user registration/forgot password:
Enabled (Requires per-user email entry at registration)
❯ Disabled (Uses SMS/TOTP as an alternative)
Please specify an SMS verification message: Your verification code is {####}
? Do you want to override the default password policy for this User Pool? No
Warning: you will not be able to edit these selections.
? What attributes are required for signing up? Email
? Specify the app's refresh token expiration period (in days): 30
? Do you want to specify the user attributes this app can read and write? No
? Do you want to enable any of the following capabilities?
? Do you want to use an OAuth flow? No
? Do you want to configure Lambda Triggers for Cognito? No

Only difference that you did with settings is to receive an authentication code for your sign in. Now that you have it, it is time for you to implement the UI.

Implement UI for the sign in page

You will be using the same components that you have created before, only for the Sign In this time.

Create a StatefulWidget named SignInScreen:

class SignInScreen extends StatefulWidget {
const SignInScreen({Key? key}) : super(key: key);
@override
State<SignInScreen> createState() => _SignInScreenState();
}
class _SignInScreenState extends State<SignInScreen> {
@override
Widget build(BuildContext context) {
return Container();
}
}

Now update the build method with the following:

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Amplify SMS Flow'),
),
body: FutureBuilder<void>(
future: _configureAmplify(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView(
children: [
OutlinedAutomatedNextFocusableTextFormField(
controller: _phoneNumberController,
labelText: 'Phone Number',
inputType: TextInputType.phone,
),
OutlinedAutomatedNextFocusableTextFormField(
controller: _passwordController,
obscureText: true,
labelText: 'Password',
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () async {
final phoneNumber = _phoneNumberController.text;
final password = _passwordController.text;
if (phoneNumber.isEmpty || password.isEmpty) {
debugPrint(
'One of the fields is empty. Not ready to submit.');
} else {
_signInUser(phoneNumber, password);
}
},
child: const Text('Sign in'),
),
),
],
);
}
if (snapshot.hasError) {
return Text('Some error happened: ${snapshot.error}');
} else {
return const Center(child: CircularProgressIndicator());
}
}),
);
}

You might realize that, you still have _configureAmplify for configuring the Amplify libraries. This should be used only when the Amplify libraries are not configured before. Otherwise all the FutureBuilder can be removed with it.

It is time to add the TextEditingController like you did before. Use the same pattern has been used in the SignUpScreen.

Now it is time to create the _signInUser method in the SignInScreen:

Future<void> _signInUser(
String phoneNumber,
String password,
) async {
final result = await Amplify.Auth.signIn(
username: phoneNumber,
password: password,
);
if (result.isSignedIn) {
debugPrint('Sign in is done.');
} else {
showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Confirm the user'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Check your phone number and enter the code below'),
OutlinedAutomatedNextFocusableTextFormField(
controller: _activationCodeController,
padding: const EdgeInsets.only(top: 16),
labelText: 'Activation Code',
inputType: TextInputType.number,
),
],
),
actions: <Widget>[
TextButton(
child: const Text('Dismiss'),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text('Confirm'),
onPressed: () {
Amplify.Auth.confirmSignIn(
confirmationValue: _activationCodeController.text,
).then((result) {
if (result.isSignedIn) {
Navigator.of(context).pop();
}
});
},
),
],
),
);
}
}

Now if you run the application, you can see that, the MFA is enabled for signing in the user.

This sums up what you can do with your phone number by using authentication libraries of AWS Amplify. Now that you are able to:

  • Login with phone number,
  • Verify the user with phone number,
  • Do MFA with phone number.

You can securely authenticate users in to your application now by using SMS flows.

For more information about the AWS Amplify authentication libraries you can check the official documentation, ask your questions at Amplify Discord. You can also check the source code for this over GitHub and if you have any questions regarding to the Amplify and Flutter topics, send it to me via DM on Twitter!

Follow Flutter Community on Twitter: https://www.twitter.com/FlutterComm

--

--