SMS authentication using Appwrite and Twilio
Tags: flutter, appwrite, twilio, authentication
Published: 2023-09-05 22:36:47

When checking out an app or service, it is exhilarating, but it can also be deflating when asked to sign up to access significant parts of that service. We can attribute this sudden change in emotions to the tedious steps associated with the user sign-up process. 

Though tedious, most sign-up procedures are necessary as they ensure the safety of the user's personal information. Let's face it; no one likes knowing their personal information is easily accessible to others, right? But come to think of it, what if there is an option where we don't have to provide much information while maintaining user information security? We like that option much better!

With [Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog), developers can integrate several authentication features into their applications with its diverse APIs and features. One feature that catches the eye is phone authentication, which uses [Appwrite's Account API](https://appwrite.io/docs/client/account?sdk=flutter-default) to create user sessions with phone numbers. This feature's authentication flow involves sending a secret code (one-time password) via an SMS provider such as **Twilio, TextMagic, Telesign, MSG91, or Vonage**. This article will use [Twilio](https://www.twilio.com/en-us?utm_source=hackmamba&utm_medium=hackmamba-blog) as our primary SMS provider and show how to set it up in Appwrite.

This article requires the reader to have a [Twilio account](https://www.twilio.com/try-twilio?utm_source=hackmamba&utm_medium=hackmamba-blog), iOS Simulator, Android Studio, or Chrome web browser to test their application, and an Appwrite instance running on Docker, [](https://marketplace.digitalocean.com/apps/appwrite)[DigitalOcean droplet](https://marketplace.digitalocean.com/apps/appwrite), or [Gitpod](https://gitpod.io/#https://github.com/appwrite/integration-for-gitpod).


> Note: Check out this [article](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9) to learn how to set up an Appwrite instance.


## Setting up Twilio in Appwrite

**Getting Twilio account information**
The purpose of creating a Twilio account is to allow us to have three things:

- **Account SID**
- **Auth Token**
- **Twilio Phone Number**

After logging in, we will need to follow the welcome steps, which guide us on how to get a custom Twilio number. In the first step, we will click on **Get Twilio Phone Number**, then scroll down to copy the information.

![welcome 1](https://paper-attachments.dropboxusercontent.com/s_673FB39A2A8C2B8C74123855FD7FA3D297D922219CCB3C496E5097DB64711AD1_1692602072089_Twilo+Snip_new1.png)

![welcome 2](https://paper-attachments.dropboxusercontent.com/s_673FB39A2A8C2B8C74123855FD7FA3D297D922219CCB3C496E5097DB64711AD1_1692602072030_Twilo+Snip_new2.png)


After copying the account information, we must update Appwrite's `.env` file with the data.

**Updating Appwrite's environmental variables**
We will head to our PC's Appwrite directory and update the `.env` file by giving the `_APP_SMS_PROVIDER` and `_APP_SMS_FROM` variables the values `sms://[ACCOUNT SID]:[AUTH TOKEN]@twilio` and `[TWILIO PHONE NUMBER]` respectively. 


![Environmental variable](https://paper-attachments.dropboxusercontent.com/s_673FB39A2A8C2B8C74123855FD7FA3D297D922219CCB3C496E5097DB64711AD1_1691938295718_environmental+variables.PNG)


After that, it is important to restart the Appwrite server after updating the `.env` file. This allows us to use the environmental variables added to the file, and we can do this using the command `docker compose up -d --force-recreate` in a terminal. The command recreates every image within the Appwrite container running on docker.


> Note: The docker service must be running, and make sure to run the command terminal within the directory of the `.env` file.

We are now ready to utilize Twilio to create our phone authentication. Thus, in the next section, we will create a Flutter application that routes to the homepage after authenticating a user's phone number. But first, we need to create an Appwrite project; Thus, head to the browser and enter the `IP address or hostname` specified when creating the Appwrite instance. Next, we must create a new project by clicking `Create Project` and filling in the project name and ID (optional).


![Create project](https://paper-attachments.dropboxusercontent.com/s_673FB39A2A8C2B8C74123855FD7FA3D297D922219CCB3C496E5097DB64711AD1_1692093051889_create+project.PNG)


**Creating a Flutter project**
In this article, we will use a Flutter template consisting of a login page with input fields, a submit button, and a homepage to route to after authentication. Therefore, run the command below in a terminal to clone the Flutter project from GitHub.

```javascript
git clone https://github.com/muyiwexy/main_twilio_auths.git
```

> Note: Check out the official GitHub [docs](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) to learn about cloning a repository.

After cloning the UI, we must obtain all the dependencies in the `pubspec.yaml` file, or there will be "some" errors. So, we can do that with the command `flutter pub get`.

The project already has the `appwrite: ^9.0.1` dependency, though, for a new project, we need to head to the `pubspec.yaml` file and add the `appwrite: ^9.0.1` to the dependencies section. Then run the `flutter pub get` command or save the file, and the command runs automatically (depending on the code editor).


![pubspec file](https://paper-attachments.dropboxusercontent.com/s_673FB39A2A8C2B8C74123855FD7FA3D297D922219CCB3C496E5097DB64711AD1_1692005094421_pubspec+file.PNG)


To run the project, we can use the command:

```javascript
Flutter run
```

After that, our template should look like the image below:


![emulator](https://paper-attachments.dropboxusercontent.com/s_673FB39A2A8C2B8C74123855FD7FA3D297D922219CCB3C496E5097DB64711AD1_1691946762728_emulator.PNG)


This template is a husk, but it will come to life in the next section as we work on adding some functionality to the application and connecting it to Appwrite.

**Creating phone authentication** **logic**
In the `appwrite_auth.dart` file, we will create a class `AppwriteAuth,` and within it, we need to initialize the **Client** instance from Appwrite.


```javascript
import 'package:appwrite/appwrite.dart';

class AppwriteAuth {
  final Client _client;
  AppwriteAuth()
      : _client = Client()
          ..setEndpoint("[endpoint or hostname]")
          ..setProject("[projectID]");
}
```

Next, we will create a `Future` function of type `Token` called `phoneLogin`. The function uses Appwrite's account API to create a session using a user's phone number by providing a phone number and ID.

```javascript
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/models.dart' as models;

class AppwriteAuth {
  final Client _client;
  AppwriteAuth()
      : _client = Client()
          ..setEndpoint("[endpoint or hostname]")
          ..setProject("[projectID]");

  Future<models.Token> phoneLogin(String phoneNumber) async {
    final account = Account(_client);
    try {
      final response = await account.createPhoneSession(
        userId: ID.unique(),
        phone: phoneNumber,
      );
      return response;
    } catch (e) {
      rethrow;
    }
  }
}
```

The function above only creates a user session, which triggers sending the secret token but doesn't verify the user. To verify the user, we will create another `Future` function of type `Session` called `verifyPhoneOTP`. The `Future` also utilizes Appwrite's account API to update a session rather than create one. This means we must provide the user's ID to check whether such a user exists and the secret token sent to verify the user. This completes our authentication flow.

```javascript
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/models.dart' as models;

class AppwriteAuth {
  final Client _client;
  AppwriteAuth()
      : _client = Client()
          ..setEndpoint("[endpoint or hostname]")
          ..setProject("[projectID]");

  Future<models.Token> phoneLogin(String phoneNumber) async {
    // do something
  }

  Future<models.Session> verifyPhoneOTP({
    required String userID,
    required String otp,
  }) async {
    final account = Account(_client);
    try {
      final session = await account.updatePhoneSession(
        userId: userID,
        secret: otp,
      );
      return session;
    } catch (e) {
      rethrow;
    }
  }
}
```

To use this function,  head to the `user_registraton.dart` file and update the `_createUserSession` with the code below:

```javascript
    import 'package:appwrite/models.dart';
    import 'package:flutter/material.dart';
    import 'package:main_twilio_auth/auth/appwrite_auth.dart';
    import 'package:main_twilio_auth/pages/home.dart';
    import 'package:main_twilio_auth/widgets/button.dart';
    import 'package:main_twilio_auth/widgets/input_form_field.dart';
    
    class UserLogIn extends StatefulWidget {
      const UserLogIn({Key? key}) : super(key: key);
      @override
      State<UserLogIn> createState() => _UserLogInState();
    }
    class _UserLogInState extends State<UserLogIn> {
      final TextEditingController _phoneController = TextEditingController();
      final TextEditingController _otpTokenController = TextEditingController();
      final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
      bool _isVisible = false;
      bool _isButtonDisabled = true;
      Token? _response;
    
      void _createUserSession() async {
        if (_formKey.currentState!.validate()) {
          _response = await AppwriteAuth().phoneLogin(_phoneController.text);
          setState(() {
            _isVisible = true;
          });
          await Future.delayed(const Duration(seconds: 5));
          setState(() {
            _isButtonDisabled = !_isButtonDisabled;
          });
        }
      }
       @override
      Widget build(BuildContext context) {
        return Placeholder();
      }
    }
```

In the code above, we added a `Token` instance `_response` from the Appwrite's models package. This allows us to map the JSON response received from its associated value to a model class, and that's shown when we call the `phoneLogin` function in the code above.

 Next, we will update the `_updateUserSession` with the code below:

```javascript
import 'package:appwrite/models.dart';
import 'package:flutter/material.dart';
import 'package:main_twilio_auth/auth/appwrite_auth.dart';
import 'package:main_twilio_auth/pages/home.dart';
import 'package:main_twilio_auth/widgets/button.dart';
import 'package:main_twilio_auth/widgets/input_form_field.dart';

class UserLogIn extends StatefulWidget {
  const UserLogIn({Key? key}) : super(key: key);
  @override
  State<UserLogIn> createState() => _UserLogInState();
}
class _UserLogInState extends State<UserLogIn> {
  final TextEditingController _phoneController = TextEditingController();
  final TextEditingController _otpTokenController = TextEditingController();
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  bool _isVisible = false;
  bool _isButtonDisabled = true;
  Token? _response;

  void _createUserSession() async {
    // do something
  }

  void _updateUserSession() async {
    if (_formKey.currentState!.validate()) {
      await AppwriteAuth().verifyPhoneOTP(
          userID: _response!.userId, otp: _otpTokenController.text);
      if (!mounted) return;
      await Navigator.of(context).pushReplacement(MaterialPageRoute(
        builder: (context) => const HomePage(),
      ));
      _phoneController.clear();
      _otpTokenController.clear();
    }
  }
   @override
  Widget build(BuildContext context) {
    return Placeholder();
  }
}
``` 

This function calls the `verifyPhoneOTP` from the `AppwriteAuth` class, checks if the current widget is still mounted, then routes by replacing the `UserLogIn` widget with the `HomePage` widget on the widget tree.

When we don't get the secret token due to some factors, the "**send otp**" button triggers a function `reSendOtp` which recalls the `phoneLogin` function.

```javascript
void _sendOtp() async {
  setState(() {
    _isButtonDisabled = true;
  });
  await AppwriteAuth().phoneLogin(_phoneController.text);
  Future.delayed(const Duration(seconds: 5), () {
    setState(() {
      _isButtonDisabled = !_isButtonDisabled;
    });
  });
}
```

**Creating an Appwrite platform and connecting Flutter to Appwrite**
After creating an Appwrite project, we will register our application by creating a new Flutter platform within the Appwrite instance. So, we will click `Flutter App`, select our desired operating system (in this case, Android), and fill in the `Name and Package Name`.


> The app's package name can be found in the app-level `build.gradle` file


![Flutter platform](https://paper-attachments.dropboxusercontent.com/s_673FB39A2A8C2B8C74123855FD7FA3D297D922219CCB3C496E5097DB64711AD1_1692093618891_Flutter+app.png)



![Android Flutter Platform](https://paper-attachments.dropboxusercontent.com/s_673FB39A2A8C2B8C74123855FD7FA3D297D922219CCB3C496E5097DB64711AD1_1692093627277_android.png)



**iOS**
First, obtain the `bundle ID` by navigating to the `project.pbxproj` file (`ios > Runner.xcodeproj > project.pbxproj`) and searching for the `PRODUCT_BUNDLE_IDENTIFIER`.

Next, head to the `Runner.xcworkspace` folder in the application's iOS folder in the project directory on [Xcode](https://developer.apple.com/xcode/). To select the runner target, choose the **Runner** project in the Xcode project navigator and find the `Runner target`. Next, select `General and IOS 11.0` as the targeting of the deployment info section.


![iOS](https://paper-attachments.dropboxusercontent.com/s_973FE6248EEDD7529E7A11435E297ADACD4E1B7483EB53A8303EEED9486C6A4E_1677590542497_file.jpeg)


**Android**
For Android, copy the XML script below and paste it below the activity tag in the `Androidmanifest.xml` file (to find this file, head to `android > app > src > main`).

{% embed https://gist.github.com/muyiwexy/f1a0f6c59329d25d37c353f34a4fa9aa %}

> Note: change [PROJECT-ID] to the ID you used when creating the Appwrite project.

Finally, running the application should have our result like the gif below:


![Result](https://paper-attachments.dropboxusercontent.com/s_673FB39A2A8C2B8C74123855FD7FA3D297D922219CCB3C496E5097DB64711AD1_1692092303060_twilio_messaging_gif.gif)



## Conclusion

This tutorial has shown how to create a phone authentication in Flutter using [Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) as the backend service and [Twilio](https://www.twilio.com/en-us?utm_source=hackmamba&utm_medium=hackmamba-blog) as an SMS provider. Authentication is a focal point in app development, and innovations to simplify the process are always happening. Appwrite has simplified this process by cutting the extra work of creating an SMTP flow or token, which is just cumbersome to a simple copy-and-paste job while ensuring user data security data.

## Resources
- [The benefits of using Appwrite Cloud for your authentication needs](https://dev.to/hackmamba/the-benefits-of-using-appwrite-cloud-for-your-authentication-needs-4m90)
[](https://dev.to/hackmamba/a-beginners-guide-to-oauth-20-with-google-sign-in-enhance-android-app-authentication-2mhi)- [A beginner's guide to OAuth 2.0 with Google sign-in: Enhance Android app authentication](https://dev.to/hackmamba/a-beginners-guide-to-oauth-20-with-google-sign-in-enhance-android-app-authentication-2mhi)
- [Phone Authentication with Appwrite and Vonage](https://dev.to/appwrite/phone-authentication-with-appwrite-and-vonage-1ep0)
[](https://dev.to/hackmamba/the-benefits-of-using-appwrite-cloud-for-your-authentication-needs-4m90)