Creating Flutter app with AWS serverless backend — Part 2: Login with phone number and OTP

Ilya Bershadskyi
4 min readMar 28, 2021
Flutter + AWS Amplify + Cognito

In the previous part we have Implemented login with social networks. Now I also want my users to login with phone number and sms code(one time password or OTP). So that when user logs in with phone number server generates a random 6 digit code and send it via SMS to user’s phone. Then user should enter the same code to perform login.

There is no standard way to achieve this with Cognito, you can have second factor authentication done by sms message but password is still required for signin. Also I don’t want to use AWS to deliver SMS because it is not the cheapest provider for my case.

To allow user login without password and send sms code using third-party provider you need to implement a custom authentication flow. For this you need backend implemented by a set of 4 lambda functions that you will embed into Cognito authentication flow.

Cognito offers us a set of lambda triggers we will use to implement this:

All user pool triggers

Out of this list we are going to use “Pre sign-up”, “Define Auth Challenge”, “Create Auth Challenge” and “Verify Auth Challenge Response”. Let’s take a look at the code I wrote for them:

This handler is not really mandatory. I added it t make all my users confirmed by default and also mark their phones as verified. If you don’t add it you will need to confirm users before they can sign-in.

As you can see everything I do here is modifying a couple of fields in event object. Cognito will pass an appropriate event object to your lambda functions and you should return the same object back with your changes. You can read more here: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-events.html and here https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html

Let’s move to create auth challenge:

This is the place where we generate the code user will need to provide to confirm his login(the challenge). You are free to do it whatever way you want. This also holds for code delivery to user. In my example I generate random 6 digits code and send it via SMS message using external API. I also added a test number of all zeros where the challenge is always 111111 so I can login during testing without SMS.

verify_auth_challenge is where you need to tell Cognito weather user provided code is correct. In my case I simply compare it to expected answer which I already know from create_auth_challenge function but it is also possible to perform other checks here.

define_auth_challange is probably the most complicated handler. It is called to initiate the custom authentication flow but also here we need to tell Cognito if auth was successful(based on challengeResult flag we get after verify_auth_challenge).

And this is how I define my user pool handlers in my serverless.yml

I don’t create a user pool with serverless, instead I created it on previous step with Amplify. This is why I have existing: true here. Without it serverless will create a new user pool and add handlers to it. Now you can deploy your lambda functions to the cloud using:

serverless deploy

After it your user pool triggers page should look approximately like this:

User pool triggers page

Now it is time to implement passwordless sign-in in our Flutter app. As our user is auto-confirmed after user registered he can login so we can implement login and registration with the same method:

Amplify still requires us to send password even if we don’t use it but I don’t consider it a problem.

To provide the sms code response to Cognito we need to do the following:

SignInResult result = await Amplify.Auth.confirmSignIn(
confirmationValue: "Cofirmation text here");
if (result.isSignedIn) {
// Success!
} else {
// Something went wrong, handle error
}

There is one more change you need to make in Flutter code: I don’t manage to make Amplify to generate configuration for custom auth flow so this we need to change manually.

Open your amplifyconfiguration.dart find there authenticationFlowType key and change it’s value to CUSTOM_AUTH so this entry looks like:

"Auth": {
"Default": {
"OAuth": {
...
},
"authenticationFlowType": "CUSTOM_AUTH"
}
}

Now you can compile your app and try to register and login with the phone number.

--

--