Phone Authentication Using Firebase

Hello Guys.

Today We are going to learn about Phone Authentication Using Firebase.

Introduction:

Firebase provides phone authentication using SMS. The normal plan of Firebase includes 10k of free SMS for a month. We will learn Phone Authentication Using Firebase in Flutter in this article. We will start from Firebase and will programmatically set up the actual integration. So, let’s start!.

Step 1:

The first step is to create a new application in a flutter.

Step 2:

Now, we will set up a project in Google Firebase. Follow the below steps for that.

• Go to Firebase Console(https://console.firebase.google.com/u/0/) and add a new project. I will share the screenshot of how it looks so you will properly understand.

• Click on “Add Project” to create a new project in Firebase.

• After Add Project. We will Enter Project Name and Click On “Continue”.

• After clicking on Continue,  Here We will Select “Default Account for Firebase” and Click “Create Project”, It will take some time to create a new project and we will move to Overview Page.

• After creating Project We will see this screen. here it’s will create an android or ios project setup. Now, We Will add a new Android project by clicking on the Android Icon. if you want to create an IOS project so you will select IOS Icon in the same Project.

• In This Project Overview add an Android app and for that, click on the Android icon. Here we will see the new form. Please check the below screenshot.

• We will find the Android package name in the AndroidManifest.xml file in Android => App => main folder of your project and enter an Android package name filed.

• We will enter an app nickname(optional)

• The most important for phone authentication is SHA Key. We will generate SHA Key from this link (https://developers.google.com/android/guides/client-auth) and Enter the SHA key in Field.

• In step 2, download google-service.json and set it in the Android => App folder of your project

• In step 3, you can see that we need to configure some dependencies.

Project-level build.gradle (android/build.gradle): means the build.gradle file should be set in the Android folder directly.

buildscript {
dependencies {
 // Add this line
classpath 'com.google.gms:google-services:4.2.0'
  }
}

App-level build.gradle (//build.gradle): means build.gradle file in Android = > App folder

// Add to the bottom of the file
apply plugin: 'com.google.gms.google-services'
Step 3:

Now, we need to enable the Phone Sign-In method in Firebase. For that, we 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 below screenshot.


Yay! we are all done with the Firebase setup.

Step 4:

Now We are starting coding structure.
Move to the project and open the pubspec.yaml file in the project. Pubspec.yaml is used to define all the dependencies of the project.

  • Add the below dependencies in your pubspec.yaml file and save the file.
  • firebase_auth: ^3.3.4
    firebase_core: ^1.10.6
    provider: ^6.0.1

    Please check the below screenshot. You will get properly understand where to add the dependency.

  •  Run “flutter pub get” in terminal OR If you are using Visual Studio Code then after saving the file it will automatically run the flutter pub get command.
  • Now, we have added all dependencies on the project side as well…. 🙂

Here You can see that. this is my lib folder. I have created three folders inside the lib folder, provider, UI, and utils. In the provider folder inside I have created a login provider file. login screen and verification screen design I have created in Ui Folder. and I have created firebase functionality inside utils folder. We will start coding for main.dart.

Here You Can see my “main.dart” file. Here I have initialized firebase for phone authentication. And next, I have created in for “firebase excauth_exception_utils.dart”.

main.dart

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:phone_auth/ui/login_screen.dart';

void main()  async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Phone Authentication',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LoginScreen()
    );
  }
}

Here you can see that I have handled the response of Firebase on this screen. and then after I have created in for “firebase_auth utils.dart”.

firebase_excauth_exception_utils.dart

import 'package:firebase_auth/firebase_auth.dart';

class FirebaseAuthHandlExceptionsUtils {
  String handleException(FirebaseAuthException firebaseAuthException) {
    String message = '';
    print(firebaseAuthException.code);
    switch (firebaseAuthException.code) {
      case 'network-request-failed':
        message = 'Please check your mobile internet connection !';
        break;
      case 'invalid-verification-code':
        message = 'Please enter a valid code';
        break;
      case 'too-many-requests':
        message =
            'Please try again after some time. You have used multiple time verification requests!';
        break;
      default:
        message = firebaseAuthException.message;
    }
    return message;
  }
}

Here Can see that I have created four functions in one class for phone authentication. The first function is providing current user details. The Second function is log in. in this function is verifying a phone number and sending OTP in your phone number. and The third function is verification. This function is matching your OTP with your current phone number. and the last function is providing the current user’s phone number.now we are create custom dialog box “dialog_utils.dart”.

firebase_auth_utils.dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';

class FirebaseAuthUtil {
  static FirebaseAuth _firebaseAuth = FirebaseAuth.instance;

  static User get currentUser {
    return _firebaseAuth.currentUser;
  }

  Future<void> login(
      {@required
          String mobileNumber,
      @required
          ValueChanged<String> onCodeSent,
      @required
          ValueChanged<FirebaseAuthException> onVerificationFailed}) async {
    final PhoneVerificationCompleted verificationCompleted =
        (AuthCredential phoneAuthCredential) {};

    final PhoneVerificationFailed verificationFailed =
        (FirebaseAuthException authException) {
      onVerificationFailed(authException);
    };
    final PhoneCodeSent codeSent =
        (String verificationId, [int forceResendingToken]) {
      onCodeSent(verificationId);
    };
    final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout =
        (String verificationId) {};
    return _firebaseAuth.verifyPhoneNumber(
      phoneNumber: '+$mobileNumber',
      timeout: Duration(seconds: 60),
      verificationCompleted: verificationCompleted,
      verificationFailed: verificationFailed,
      codeSent: codeSent,
      codeAutoRetrievalTimeout: codeAutoRetrievalTimeout,
    );
  }

  Future<void> verifyOTPCode(
      {@required String verificationId,
      @required String verificationCode,
      @required ValueChanged<User> onVerificationSuccess,
      @required ValueChanged<String> onCodeVerificationFailed}) async {
    AuthCredential credential = PhoneAuthProvider.credential(
        verificationId: verificationId, smsCode: verificationCode);
    await _firebaseAuth.signInWithCredential(credential).then((value) {
      if (value.user != null) {
        onVerificationSuccess(value.user);
      } else {
        onCodeVerificationFailed('VerificationFailed');
      }
    }).catchError((error) {
      onCodeVerificationFailed(error.toString());
    });
  }

  static Future<void> logout() async {
    await FirebaseAuth.instance.signOut();
  }

  static String get phoneNumberWithPlusSymbol {
    return FirebaseAuth.instance.currentUser.phoneNumber;
  }
}

Here You can see that I have created a custom Alertbox on this screen. now we create a login provider screen”login_provider.dart”.

dialog_utils.dart

import 'package:flutter/material.dart';

class DialogUtils {
  static Future<void> showCustomMessageDialog(
      BuildContext context, String message,
      {double fontSize, Color textColor}) async {
    return await showDialog(
        context: context,
        barrierDismissible: false,
        builder: (context) {
          return AlertDialog(
            shape:
            RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
            content: Text(message),
            actions: <Widget>[
              FlatButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: Text(
                  'Ok',
                  style: TextStyle(fontSize: fontSize, color: textColor),
                ),
              )
            ],
          );
        });
  }

  static Future<void> showLoadingDialogWithMessage(
      BuildContext context, String message) {
    return showDialog(
        context: context,
        barrierDismissible: false,
        builder: (context) {
          return AlertDialog(
            shape:
            RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
            title: Text(
              message,
              style: TextStyle(fontSize: 16),
              textAlign: TextAlign.center,
            ),
            content: Container(height: 50, child: LoadingWidget()),
          );
        });
  }
}

class LoadingWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: CircularProgressIndicator(),
    );
  }
}

Here You can see some funactionality for login. in this screen is connect to “firebase_auth_utils.dart” and “firebase_excauth_exception_utils.dart”. now we are create a login screen “login_screen.dart”.

login_provider.dart

import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:phone_auth/utils/firebase_auth_util.dart';
import 'package:phone_auth/utils/firebase_excauth_exception_utils.dart';

class LoginProvider extends ChangeNotifier {

  LoginProvider();

  final _firebaseAuthUtil = FirebaseAuthUtil();

  String _verificationId = '';

  bool _isOtpSending = false;
  bool get isOtpSending => _isOtpSending;
  set _setIsOtpSending(bool value) {
    if (value != _isOtpSending) {
      _isOtpSending = value;
      notifyListeners();
    }
  }

  bool _isOtpVerifying = false;
  bool get isOtpVerifying => _isOtpVerifying;
  set _setIsOtpVerifying(bool value) {
    if (value != _isOtpVerifying) {
      _isOtpVerifying = value;
      notifyListeners();
    }
  }


  void login(
      {@required String phoneNumber,
      @required GestureTapCallback onCodeSent,
      @required ValueChanged<String> onFailure}) async {
    try {
      _setIsOtpSending = true;
      await sendFirebaseOTP(
          phoneNumber: phoneNumber,
          onCodeSent: onCodeSent,
          onFailure: onFailure);
    } catch (error) {
      _setIsOtpSending = false;
      onFailure(error.toString());
    }
  }

  Future<void> sendFirebaseOTP(
      {@required String phoneNumber,
      @required GestureTapCallback onCodeSent,
      @required ValueChanged<String> onFailure}) async {
    _setIsOtpSending = true;
    _firebaseAuthUtil.login(
        mobileNumber: phoneNumber,
        onCodeSent: (value) {
          _verificationId = value;
          _setIsOtpSending = false;
          onCodeSent();
        },
        onVerificationFailed: (value) {
          _setIsOtpSending = false;
          onFailure(FirebaseAuthHandlExceptionsUtils().handleException(value));
        });
  }

  Future<void> verifyFirebaseOTP(
      {@required String verificationCode,
      @required ValueChanged<User> onOtpVerified,
      @required ValueChanged<String> onFailure}) async {
    _setIsOtpVerifying = true;
    try {
      await _firebaseAuthUtil.verifyOTPCode(
          verificationId: _verificationId,
          verificationCode: verificationCode,
          onVerificationSuccess: (value) {
            _verificationDone(
                phoneNumber: value.phoneNumber,
                onFailure: onFailure,
                onDeviceTokenUpdated: () {
                  onOtpVerified(value);
                });
          },
          onCodeVerificationFailed: (value) {
            _setIsOtpVerifying = false;
            onFailure(value);
          });
    } catch (error) {
      _setIsOtpVerifying = false;
      onFailure(error.toString());
    }
  }

  Future<void> _verificationDone(
      {@required String phoneNumber,
      @required ValueChanged<String> onFailure,
      @required GestureTapCallback onDeviceTokenUpdated}) async {
    try {
      _setIsOtpVerifying = false;
      onDeviceTokenUpdated();
    } catch (error) {
      await FirebaseAuth.instance.signOut();
      _setIsOtpVerifying = false;
      onFailure(error.toString());
    }
  }
}

Here You can see the login screen. now we are creating a verification screen “verification_screen.dart”.

login_screen.dart

import 'package:flutter/material.dart';
import 'package:phone_auth/Provider/login_provider.dart';
import 'package:phone_auth/ui/verification_screen.dart';
import 'package:phone_auth/utils/dialog_utils.dart';
import 'package:provider/provider.dart';

class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
 
  String _phoneNumber = '';
 

  @override
  void initState() {
    super.initState();
  }

 
  void _onloginPressed(LoginProvider loginProvider) {
    if (_phoneNumber.length < 10) {
      DialogUtils.showCustomMessageDialog(
          context, 'Please Enter valid phone number');
    } else {
      loginProvider.login(
          phoneNumber: "+91"+ _phoneNumber,
          onCodeSent: () {
            Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (context) => ChangeNotifierProvider<LoginProvider>.value(
                  value: loginProvider,
                  child: VerificationPage(
                      phoneNumber:"+91" + _phoneNumber))));
          },
          onFailure: (error) {
            DialogUtils.showCustomMessageDialog(context, error);
          });
    }
  }

  @override
  Widget build(BuildContext context) {
    var height = MediaQuery.of(context).size.height;
    return Scaffold(
      backgroundColor: Colors.white,
      body: GestureDetector(
        onTap: () {
        },
        child: SingleChildScrollView(
          child: Container(
            height: height,
            child: ChangeNotifierProvider<LoginProvider>(
              create: (_) => LoginProvider(),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: <Widget>[
                  Container(
                    height: height / 2.5,
                    child: Padding(
                      padding: const EdgeInsets.all(14.0),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Row(
                            children: [
                              Text("Log In",style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),),
                            ],
                          ),
                          SizedBox(height: 20),
                          TextFormField(
                            decoration: const InputDecoration(
                              border: OutlineInputBorder(
                                borderSide: BorderSide(color: Colors.black)
                              ),
                              focusedBorder: OutlineInputBorder(
                                borderSide: BorderSide(color: Colors.black),
                              ),
                              enabledBorder: const OutlineInputBorder(
                                borderSide: const BorderSide(color: Colors.black),
                              ),
                              labelText: 'Enter Phone Number',
                              labelStyle: TextStyle(color:Colors.grey)
                            ),
                            onChanged: (val) {
                              _phoneNumber = val;
                            },
                          ),
                          SizedBox(height: 20.0),
                          Consumer<LoginProvider>(
                            builder: (context, loginProvider, _) =>
                                InkWell(
                                  child:
                                Container(
                                  alignment: Alignment.center,
                                  padding: EdgeInsets.all(10.0),
                                  decoration: BoxDecoration(borderRadius: BorderRadius.circular(5),
                                  color: Colors.black),
                                    child: Text("LOG IN",style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white),)),
                                  onTap: loginProvider.isOtpSending
                                      ? null
                                      : () {
                                    _onloginPressed(loginProvider);
                                  },
                                )),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Here you can see the verification screen. in this screen is verified OTP. if OTP is verified then we are move-in “Home Screen.dart”. otherwise, show an error on this screen.

verification_screen.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:phone_auth/Provider/login_provider.dart';
import 'package:phone_auth/utils/dialog_utils.dart';
import 'package:provider/provider.dart';
import 'home_scren.dart';

class VerificationPage extends StatefulWidget {
  final String phoneNumber;

  VerificationPage({@required this.phoneNumber});

  @override
  _VerificationPageState createState() => _VerificationPageState();
}

class _VerificationPageState extends State<VerificationPage> {
  String _verificationCode = '';
  Timer _codeResendTimer;
  int _codeResendTime = 0;
  set _setCodeResendTime(int time) {
    if (mounted) {
      setState(() {
        _codeResendTime = time;
      });
    }
  }

  @override
  void initState() {
    _initializeCodeResendTimer;
    super.initState();
  }



  void get _initializeCodeResendTimer {
    _codeResendTimer?.cancel();
    _codeResendTime = 60;
    _codeResendTimer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (_codeResendTime > 0) {
        _setCodeResendTime = --_codeResendTime;
      } else {
        timer?.cancel();
      }
    });
  }

  void _onVerifyPressed() {
    final loginProvider = Provider.of<LoginProvider>(context, listen: false);
    if (!loginProvider.isOtpSending && !loginProvider.isOtpVerifying) {
      if (_verificationCode.length < 6) {
        DialogUtils.showCustomMessageDialog(
            context, 'Please enter complete verification code');
      } else {
        DialogUtils.showLoadingDialogWithMessage(context, 'Verifying code');
        loginProvider.verifyFirebaseOTP(
            verificationCode: _verificationCode,
            onOtpVerified: (value) async {
              Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) => HomeScreen()));
            },
            onFailure: (value) {
              Navigator.pop(context);
            });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: null,
      body: Stack(
        children: [
          Container(
            child: Padding(
              padding:  EdgeInsets.only(left: 20.0, right: 20, top: 50),
              child: Column(
                children: [
                  SizedBox(
                    height: 40,
                  ),
                  Row(
                    children: [
                      Text("Enter Verifiaction Code",
                          style: TextStyle(
                              fontSize: 20, fontWeight: FontWeight.bold)),
                    ],
                  ),
                  SizedBox(
                    height: 10,
                  ),
                  Row(
                    children: [
                      Expanded(
                          child: Text(
                              "Please Enter Your Verification Code below, the code sent to your phone number ${widget.phoneNumber}",
                              style: TextStyle(fontSize: 16))),
                    ],
                  ),
                  SizedBox(
                    height: 40,
                  ),
                  Container(
                    width: MediaQuery.of(context).size.width,
                    height: 100,
                    child: Column(children: [
                      Container(
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(5),
                          border: Border.all(
                            color: Colors.grey,
                          ),
                        ),
                        child: Consumer<LoginProvider>(
                          builder: (context, loginProvider, _) => TextField(
                              enabled: !(loginProvider.isOtpSending ||
                                  loginProvider.isOtpVerifying),
                              style: TextStyle(
                                fontSize: 32.0,
                                color: Colors.black,
                                fontWeight: FontWeight.bold,
                              ),
                              keyboardType: TextInputType.number,
                              decoration: InputDecoration(
                                hintStyle: TextStyle(fontSize: 17),
                                hintText: 'Enter Verification Code',
                                border: InputBorder.none,
                                contentPadding: EdgeInsets.only(left:10, bottom: 10.0),
                              ),
                              onChanged: (verificationCode) {
                                if (verificationCode.length == 6) {
                                  _verificationCode = verificationCode;
                                  _onVerifyPressed();
                                }
                              }),
                        ),
                      ),
                    ]),
                  ),
                  SizedBox(
                    height: 10,
                  ),
                  ButtonTheme(
                    height: 40,
                    highlightColor: Colors.transparent,
                    child: Consumer<LoginProvider>(
                      builder: (context, loginProvider, _) => InkWell(
                        child: Text(
                          _codeResendTime == 0
                              ? "Resend Code".toUpperCase()
                              : 'Resend Code In $_codeResendTime',
                          style: _codeResendTime == 0
                              ? TextStyle(
                              color: Colors.grey,
                                  fontWeight: FontWeight.w600)
                              : TextStyle(
                                  color: Colors.grey,
                                  fontWeight: FontWeight.w600),
                        ),
                        onTap: loginProvider.isOtpSending ||
                                loginProvider.isOtpVerifying ||
                                _codeResendTime > 0
                            ? null
                            : () {
                                // DialogUtils.showLoadingDialogWithMessage(
                                //     context, 'Resending code');
                                loginProvider.sendFirebaseOTP(
                                    phoneNumber: widget.phoneNumber,
                                    onCodeSent: () {
                                      Navigator.pop(context);
                                      _initializeCodeResendTimer;
                                    },
                                    onFailure: (value) {
                                      Navigator.pop(context);
                                      // DialogUtils.showCustomMessageDialog(
                                      //     context, value);
                                    });
                              },
                      ),
                    ),
                  )
                ],
              ),
            ),
          ),
          Consumer<LoginProvider>(
            builder: (context, loginProvider, _) => Positioned(
              bottom: 0,
              left: 0,
              right: 0,
              child: MaterialButton(
                height: kBottomNavigationBarHeight +
                    MediaQuery.of(context).padding.bottom,
                color: Colors.black,
                // disabledColor: kMainColor,
                minWidth: double.maxFinite,
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text(
                      'Verify Code'.toUpperCase(),
                      style: TextStyle(color: Colors.white, fontSize: 20),
                    ),
                  ],
                ),
                onPressed:
                    loginProvider.isOtpVerifying || loginProvider.isOtpSending
                        ? null
                        : _onVerifyPressed,
              ),
            ),
          )
        ],
      ),
    );
  }

  @override
  void dispose() {
    _codeResendTimer?.cancel();
    super.dispose();
  }
}

Removing reCAPTCHA Screen

While running the above code you have seen the following screen which is shown before receiving OTP.

To remove this reCAPTCHA in this screen, you need to add an SHA-256 fingerprint to your Firebase project settings as mentioned in the setup section of this story. Then navigate to your Firebase project settings again and click “App Check”.

In the app check section overview, you will see that there is no problem if the status next to your app name is shown as “Register” but it shows on ‘Unregistered’. All you have to do is tap on your application name and then click. Safety net. It will expand the layout, now add SHA-256 (if not previously added), and agree to the terms and conditions by checking and saving the checkbox.

Now your app status under overview will be updated to “Registered” and the reCAPTCHA screen will be removed from your app.

Preparing for Release: In case you are preparing your app for release mode then you need to add SHA fingerprints (present in the play store) to your Firebase project settings.

Please see my below screenshots. 

Hope you understand this article and will help you to start working on phone authentication. If you have any doubts or questions do comment.

Source Code:  https://github.com/ParthDevaliya/Phone-Authentication-Using-Firebase 

Submit a Comment

Your email address will not be published. Required fields are marked *

Subscribe

Select Categories