FirebasePhoneAuthHandler For Flutter

pub package likes popularity pub points

  • An easy-to-use firebase phone authentication package to easily send and verify OTP's with auto-fetch OTP support via SMS.
  • Supports OTP on web out of the box.

Screenshots

      

Getting Started

Step 1: Before you can add Firebase to your app, you need to create a Firebase project to connect to your application. Visit Understand Firebase Projects to learn more about Firebase projects.

Step 2: To use Firebase in your app, you need to register your app with your Firebase project. Registering your app is often called "adding" your app to your project.

Also, register a web app if using on the web. Follow on the screen instructions to initilalize the project.

Add the latest version 'firebase-auth' CDN from here. (Tested on version 8.6.1)

Step 3: Add a Firebase configuration file and the SDK's. (google-services)

Step 4: When the basic setup is done, open the console and then the project and head over to Authentication from the left drawer menu.

Step 5: Click on Sign-in method next to the Users tab and enable Phone.

Step 6: Follow the additional configuration steps for the platforms to avoid any errors.

Step 7: IMPORTANT: Do not forget to enable the Android Device Verification service from Google Cloud Platform. (make sure the correct project is selected).

Step 8: Lastly, add firebase_core as a dependency in your pubspec.yaml file. and call Firebase.initializeApp() in the main method as shown:

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

Usage

To use this plugin, add firebase_phone_auth_handler as a dependency in your pubspec.yaml file.

  dependencies:
    flutter:
      sdk: flutter
    firebase_phone_auth_handler:

First and foremost, import the widget.

import 'package:firebase_phone_auth_handler/firebase_phone_auth_handler.dart';

Wrap the MaterialApp with FirebasePhoneAuthProvider to enable your application to support phone authentication like shown.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FirebasePhoneAuthProvider(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        home: HomeScreen(),
      ),
    );
  }
}

You can now add a FirebasePhoneAuthHandler widget to your widget tree and pass all the required parameters to get started.

FirebasePhoneAuthHandler(
    phoneNumber: "+919876543210",
    builder: (context, controller) {
        return SizedBox.shrink();
    },
),

The phone number is the number to which the OTP will be sent which should be formatted in the following way:

+919876543210 - where +91 is the country code and 9876543210 is the phone number.

The widget returned from the builder is rendered on the screen. The builder exposes a controller which contains various variables and methods.

Callbacks such as onLoginSuccess or onLoginFailed can be passed to the widget.

onLoginSuccess is called whenever the otp was sent to the mobile successfully and was either auto verified or verified manually by calling verifyOTP function in the controller. The callback exposes UserCredential object which can be used to find user UID and other stuff. The boolean provided is whether the OTP was auto verified or verified manually be calling verifyOTP. True if auto verified and false is verified manually.

onLoginFailed is called if an error occurs while sending OTP or verifying the OTP or any internal error occurs, callback is triggered exposing FirebaseAuthException which can be used to handle the error.

FirebasePhoneAuthHandler(
    phoneNumber: "+919876543210",
    builder: (context, controller) {
      return SizedBox.shrink();
    },
    onLoginSuccess: (userCredential, autoVerified) {
      print("autoVerified: $autoVerified");
      print("Login success UID: ${userCredential.user?.uid}");
    },
    onLoginFailed: (authException) {
      print("An error occurred: ${authException.message}");
    },
),

To logout the current user(if any), simply call

await FirebasePhoneAuthHandler.signOut(context);

controller.signOut() can also be used to logout the current user if the functionality is needed in the same screen as the widget itself (where controller is the variable passed in the callback from the builder method in the widget).

Web (reCAPTCHA)

By default, the reCAPTCHA widget is a fully managed flow which provides security to your web application. The widget will render as an invisible widget when the sign-in flow is triggered. An "invisible" widget will appear as a full-page modal on-top of your application like demonstrated below.

reCAPTCHA1

Although, a RecaptchaVerifier instance can be passed which can be used to manage the widget.

Use the function recaptchaVerifierForWebProvider in FirebasePhoneAuthHandler which gives a boolean to check whether the current platform is Web or not.

NOTE: Do not pass a RecaptchaVerifier instance if the platform is not web, else an error occurs.

Example:

recaptchaVerifierForWebProvider: (isWeb) {
    if (isWeb) return RecaptchaVerifier();
},

It is however possible to display an inline widget which the user has to explicitly press to verify themselves.

reCAPTCHA2

To add an inline widget, specify a DOM element ID to the container argument of the RecaptchaVerifier instance. The element must exist and be empty otherwise an error will be thrown. If no container argument is provided, the widget will be rendered as "invisible".

RecaptchaVerifier(
  container: 'recaptcha',
  size: RecaptchaVerifierSize.compact,
  theme: RecaptchaVerifierTheme.dark,
  onSuccess: () => print('reCAPTCHA Completed!'),
  onError: (FirebaseAuthException error) => print(error),
  onExpired: () => print('reCAPTCHA Expired!'),
),

If the reCAPTCHA badge does not disappear automatically after authentication is done, try adding the following code in onLoginSuccess so that it dissapears when the login process is done.

Firstly import querySelector from dart:html.

import 'dart:html' show querySelector;

Then add this in onLoginSuccess callback.

final captcha = querySelector('#__ff-recaptcha-container');
if (captcha != null) captcha.hidden = true;

If you want to completely disable the reCAPTCHA badge (typically appears on the bottom right), add this CSS style in the web/index.html outside any other tag.

<style>
    .grecaptcha-badge { visibility: hidden; }
</style>

Sample Usage

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_phone_auth_handler/firebase_phone_auth_handler.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FirebasePhoneAuthProvider(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        home: HomeScreen(),
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  String? _enteredOTP;
  static const _phoneNumber = "+919876543210";

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: FirebasePhoneAuthHandler(
        phoneNumber: _phoneNumber,
        timeOutDuration: const Duration(seconds: 60),
        onLoginSuccess: (userCredential, autoVerified) async {
          print(autoVerified
              ? "OTP was fetched automatically"
              : "OTP was verified manually");

          print("Login Success UID: ${userCredential.user?.uid}");
        },
        onLoginFailed: (authException) {
          print("An error occurred: ${authException.message}");

          // handle error further if needed
        },
        builder: (context, controller) {
          return Scaffold(
            appBar: AppBar(
              title: Text("Verification Code"),
              backgroundColor: Colors.black,
              actions: controller.codeSent
                  ? [
                      TextButton(
                        child: Text(
                          controller.timerIsActive
                              ? "${controller.timerCount.inSeconds}s"
                              : "RESEND",
                          style: TextStyle(color: Colors.blue, fontSize: 18),
                        ),
                        onPressed: controller.timerIsActive
                            ? null
                            : () async {
                                await controller.sendOTP();
                              },
                      ),
                      SizedBox(width: 5),
                    ]
                  : null,
            ),
            body: controller.codeSent
                ? ListView(
                    padding: EdgeInsets.all(20),
                    children: [
                      Text(
                        "We've sent an SMS with a verification code to $_phoneNumber",
                        style: TextStyle(
                          fontSize: 25,
                        ),
                      ),
                      SizedBox(height: 10),
                      Divider(),
                      AnimatedContainer(
                        duration: Duration(seconds: 1),
                        height: controller.timerIsActive ? null : 0,
                        child: Column(
                          children: [
                            CircularProgressIndicator(),
                            SizedBox(height: 50),
                            Text(
                              "Listening for OTP",
                              textAlign: TextAlign.center,
                              style: TextStyle(
                                fontSize: 25,
                                fontWeight: FontWeight.w600,
                              ),
                            ),
                            Divider(),
                            Text("OR", textAlign: TextAlign.center),
                            Divider(),
                          ],
                        ),
                      ),
                      Text(
                        "Enter Code Manually",
                        style: TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.w600,
                        ),
                      ),
                      TextField(
                        maxLength: 6,
                        keyboardType: TextInputType.number,
                        onChanged: (String v) async {
                          _enteredOTP = v;
                          if (this._enteredOTP?.length == 6) {
                            final res =
                                await controller.verifyOTP(otp: _enteredOTP!);
                            // Incorrect OTP
                            if (!res)
                              print(
                                "Please enter the correct OTP sent to $_phoneNumber",
                              );
                          }
                        },
                      ),
                    ],
                  )
                : Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      CircularProgressIndicator(),
                      SizedBox(height: 50),
                      Center(
                        child: Text(
                          "Sending OTP",
                          style: TextStyle(fontSize: 25),
                        ),
                      ),
                    ],
                  ),
            floatingActionButton: controller.codeSent
                ? FloatingActionButton(
                    backgroundColor: Theme.of(context).accentColor,
                    child: Icon(Icons.check),
                    onPressed: () async {
                      if (_enteredOTP == null || _enteredOTP?.length != 6) {
                        print("Please enter a valid 6 digit OTP");
                      } else {
                        final res =
                            await controller.verifyOTP(otp: _enteredOTP!);
                        // Incorrect OTP
                        if (!res)
                          print(
                            "Please enter the correct OTP sent to $_phoneNumber",
                          );
                      }
                    },
                  )
                : null,
          );
        },
      ),
    );
  }
}

Created & Maintained By Rithik Bhandari

Libraries

firebase_phone_auth_handler