Introduction

Google Firebase provides phone authentication using SMS. The basic plan of Firebase includes 10k of free SMSes for a month. We will learn Firebase Phone Authentication in Flutter in this article. We will start from Firebase and will programmatically set up the actual integration in Flutter. So, let’s start!
What we will achieve.
OTP Authentication in Flutter using Firebase
Step 1
The first and most basic step is to create a new application in Flutter. If you are a beginner in Flutter, then you can check my blog Create your first app in Flutter. I have created an app named “flutter_otp_auth”.
Step 2
Now, you need to set up a project in Google Firebase. Follow the below steps for that. Please follow the steps very carefully.
Project-level build.gradle (<project>/build.gradle): means the build.gradle file should be put in the Android folder directly.
  1. buildscript {
  2. dependencies {
  3. // Add this line
  4. classpath 'com.google.gms:google-services:4.2.0'
  5. }
  6. }
App-level build.gradle (<project>/<app-module>/build.gradle): means build.gradle file in Android = > App folder
  1. // Add to the bottom of the file
  2. apply plugin: 'com.google.gms.google-services’
Note
We do not need to add the implementation 'com.google.firebase:firebase-core:16.0.9' in dependencies,
Step 3
Now, you need to enable the Phone Sign-In method in Firebase. For that, you need to go to the Authentication tab and then, the Sign-in method tab. From there, enable the Phone Sign-in method. Please check the screenshot.
OTP Authentication in Flutter using Firebase
You are all done with Firebase set up. Congratulations!
Step 4
Get back to the project and open the pubspec.yaml file in the root of the project. Pubspec.yaml is used to define all the dependencies and assets of the project.
Step 5
Now, we need to programmatically handle OTP Login in Google Firebase. For that, we create 2 pages - main.dart(default) and homepage.dart. I have attached a link of Git repo at the bottom of the article. You can take reference from there. Here, I will just import methods for sending and verifying the OTP. Below is the source code for the dart file.
  1. import 'package:flutter/material.dart';
  2. import 'package:firebase_auth/firebase_auth.dart';
  3. import 'package:flutter/services.dart';
  4. import 'homepage.dart';
  5. void main() => runApp(MyApp());
  6. class MyApp extends StatelessWidget {
  7. @override
  8. Widget build(BuildContext context) {
  9. return MaterialApp(
  10. title: 'Phone Authentication',
  11. routes: <String, WidgetBuilder>{
  12. '/homepage': (BuildContext context) => MyHome(),
  13. '/loginpage': (BuildContext context) => MyApp(),
  14. },
  15. theme: ThemeData(
  16. primarySwatch: Colors.blue,
  17. ),
  18. home: MyAppPage(title: 'Phone Authentication'),
  19. );
  20. }
  21. }
  22. class MyAppPage extends StatefulWidget {
  23. MyAppPage({Key key, this.title}) : super(key: key);
  24. final String title;
  25. @override
  26. _MyAppPageState createState() => _MyAppPageState();
  27. }
  28. class _MyAppPageState extends State<MyAppPage> {
  29. String phoneNo;
  30. String smsOTP;
  31. String verificationId;
  32. String errorMessage = '';
  33. FirebaseAuth _auth = FirebaseAuth.instance;
  34. Future<void> verifyPhone() async {
  35. final PhoneCodeSent smsOTPSent = (String verId, [int forceCodeResend]) {
  36. this.verificationId = verId;
  37. smsOTPDialog(context).then((value) {
  38. print('sign in');
  39. });
  40. };
  41. try {
  42. await _auth.verifyPhoneNumber(
  43. phoneNumber: this.phoneNo, // PHONE NUMBER TO SEND OTP
  44. codeAutoRetrievalTimeout: (String verId) {
  45. //Starts the phone number verification process for the given phone number.
  46. //Either sends an SMS with a 6 digit code to the phone number specified, or sign's the user in and [verificationCompleted] is called.
  47. this.verificationId = verId;
  48. },
  49. codeSent:
  50. smsOTPSent, // WHEN CODE SENT THEN WE OPEN DIALOG TO ENTER OTP.
  51. timeout: const Duration(seconds: 20),
  52. verificationCompleted: (AuthCredential phoneAuthCredential) {
  53. print(phoneAuthCredential);
  54. },
  55. verificationFailed: (AuthException exceptio) {
  56. print('${exceptio.message}');
  57. });
  58. } catch (e) {
  59. handleError(e);
  60. }
  61. }
  62. Future<bool> smsOTPDialog(BuildContext context) {
  63. return showDialog(
  64. context: context,
  65. barrierDismissible: false,
  66. builder: (BuildContext context) {
  67. return new AlertDialog(
  68. title: Text('Enter SMS Code'),
  69. content: Container(
  70. height: 85,
  71. child: Column(children: [
  72. TextField(
  73. onChanged: (value) {
  74. this.smsOTP = value;
  75. },
  76. ),
  77. (errorMessage != ''
  78. ? Text(
  79. errorMessage,
  80. style: TextStyle(color: Colors.red),
  81. )
  82. : Container())
  83. ]),
  84. ),
  85. contentPadding: EdgeInsets.all(10),
  86. actions: <Widget>[
  87. FlatButton(
  88. child: Text('Done'),
  89. onPressed: () {
  90. _auth.currentUser().then((user) {
  91. if (user != null) {
  92. Navigator.of(context).pop();
  93. Navigator.of(context).pushReplacementNamed('/homepage');
  94. } else {
  95. signIn();
  96. }
  97. });
  98. },
  99. )
  100. ],
  101. );
  102. });
  103. }
  104. signIn() async {
  105. try {
  106. final AuthCredential credential = PhoneAuthProvider.getCredential(
  107. verificationId: verificationId,
  108. smsCode: smsOTP,
  109. );
  110. final FirebaseUser user = await _auth.signInWithCredential(credential);
  111. final FirebaseUser currentUser = await _auth.currentUser();
  112. assert(user.uid == currentUser.uid);
  113. Navigator.of(context).pop();
  114. Navigator.of(context).pushReplacementNamed('/homepage');
  115. } catch (e) {
  116. handleError(e);
  117. }
  118. }
  119. handleError(PlatformException error) {
  120. print(error);
  121. switch (error.code) {
  122. case 'ERROR_INVALID_VERIFICATION_CODE':
  123. FocusScope.of(context).requestFocus(new FocusNode());
  124. setState(() {
  125. errorMessage = 'Invalid Code';
  126. });
  127. Navigator.of(context).pop();
  128. smsOTPDialog(context).then((value) {
  129. print('sign in');
  130. });
  131. break;
  132. default:
  133. setState(() {
  134. errorMessage = error.message;
  135. });
  136. break;
  137. }
  138. }
  139. @override
  140. Widget build(BuildContext context) {
  141. return Scaffold(
  142. appBar: AppBar(
  143. title: Text(widget.title),
  144. ),
  145. body: Center(
  146. child: Column(
  147. mainAxisAlignment: MainAxisAlignment.center,
  148. children: <Widget>[
  149. Padding(
  150. padding: EdgeInsets.all(10),
  151. child: TextField(
  152. decoration: InputDecoration(
  153. hintText: 'Enter Phone Number Eg. +910000000000'),
  154. onChanged: (value) {
  155. this.phoneNo = value;
  156. },
  157. ),
  158. ),
  159. (errorMessage != ''
  160. ? Text(
  161. errorMessage,
  162. style: TextStyle(color: Colors.red),
  163. )
  164. : Container()),
  165. SizedBox(
  166. height: 10,
  167. ),
  168. RaisedButton(
  169. onPressed: () {
  170. verifyPhone();
  171. },
  172. child: Text('Verify'),
  173. textColor: Colors.white,
  174. elevation: 7,
  175. color: Colors.blue,
  176. )
  177. ],
  178. ),
  179. ),
  180. );
  181. }
  182. }
Step 6
When you successfully verify the OTP, you can check that Google Firebase stores the user details on the server. Please check the screenshot below.
OTP Authentication in Flutter using Firebase

Possible Errors
Error
import androidx.annotation.NonNull;
Solution
Put android.useAndroidX=true
android.enableJetifier=true
In android/gradle.properties file
NOTE
Please check the Git repository for the full source code. You need to add your google-services.json file in Android >> Apps folder.

Conclusion

OTP verification becomes one of the most required authentication techniques when security is very important. Google Firebase provides OTP Phone Authentication free starter plan and Flutter provides an easy to set up technique for this.
Git Repo