Giter Site home page Giter Site logo

rithik-dev / firebase_phone_auth_handler Goto Github PK

View Code? Open in Web Editor NEW
28.0 4.0 31.0 155 KB

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

Home Page: https://pub.dev/packages/firebase_phone_auth_handler

License: MIT License

Dart 91.31% Kotlin 0.27% Swift 0.77% Objective-C 0.07% HTML 7.58%
android ios macos web pub flutter flutter-package package firebase-auth provider null-safety dart firebase firebase-app

firebase_phone_auth_handler's Introduction

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 initialize 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(_MainApp());
}

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 _MainApp 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.

onCodeSent is called when the OTP is successfully sent to the phone number.

FirebasePhoneAuthHandler(
    phoneNumber: "+919876543210",
    // If true, the user is signed out before the onLoginSuccess callback is fired when the OTP is verified successfully.
    signOutOnSuccessfulVerification: false,
    
    linkWithExistingUser: false,
    builder: (context, controller) {
      return SizedBox.shrink();
    },
    onLoginSuccess: (userCredential, autoVerified) {
      debugPrint("autoVerified: $autoVerified");
      debugPrint("Login success UID: ${userCredential.user?.uid}");
    },
    onLoginFailed: (authException, stackTrace) {
      debugPrint("An error occurred: ${authException.message}");
    },
    onError: (error, stackTrace) {},
),

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 disappears 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>

How I prefer using it usually

I usually have a phone number input field, which handles phone number input. Then pass the phone number to the VerifyPhoneNumberScreen widget from the example app.

// probably some ui or dialog to get the phone number
final phoneNumber = _getPhoneNumber();

// then call
void _verifyPhoneNumber() async {
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (_) => VerifyPhoneNumberScreen(phoneNumber: phoneNumber),
    ),
  );
}

/// route to home screen or somewhere in the onLoginSuccess callback for [VerifyPhoneNumberScreen] 

Sample Usage

import 'package:firebase_phone_auth_handler/firebase_phone_auth_handler.dart';
import 'package:flutter/material.dart';
import 'package:phone_auth_handler_demo/screens/home_screen.dart';
import 'package:phone_auth_handler_demo/utils/helpers.dart';
import 'package:phone_auth_handler_demo/widgets/custom_loader.dart';
import 'package:phone_auth_handler_demo/widgets/pin_input_field.dart';

class VerifyPhoneNumberScreen extends StatefulWidget {
  static const id = 'VerifyPhoneNumberScreen';

  final String phoneNumber;

  const VerifyPhoneNumberScreen({
    Key? key,
    required this.phoneNumber,
  }) : super(key: key);

  @override
  State<VerifyPhoneNumberScreen> createState() =>
      _VerifyPhoneNumberScreenState();
}

class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen>
    with WidgetsBindingObserver {
  bool isKeyboardVisible = false;

  late final ScrollController scrollController;

  @override
  void initState() {
    scrollController = ScrollController();
    WidgetsBinding.instance.addObserver(this);
    super.initState();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    scrollController.dispose();
    super.dispose();
  }

  @override
  void didChangeMetrics() {
    final bottomViewInsets = WidgetsBinding.instance.window.viewInsets.bottom;
    isKeyboardVisible = bottomViewInsets > 0;
  }

  // scroll to bottom of screen, when pin input field is in focus.
  Future<void> _scrollToBottomOnKeyboardOpen() async {
    while (!isKeyboardVisible) {
      await Future.delayed(const Duration(milliseconds: 50));
    }

    await Future.delayed(const Duration(milliseconds: 250));

    await scrollController.animateTo(
      scrollController.position.maxScrollExtent,
      duration: const Duration(milliseconds: 250),
      curve: Curves.easeIn,
    );
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: FirebasePhoneAuthHandler(
        phoneNumber: widget.phoneNumber,
        signOutOnSuccessfulVerification: false,
        linkWithExistingUser: false,
        autoRetrievalTimeOutDuration: const Duration(seconds: 60),
        otpExpirationDuration: const Duration(seconds: 60),
        onCodeSent: () {
          log(VerifyPhoneNumberScreen.id, msg: 'OTP sent!');
        },
        onLoginSuccess: (userCredential, autoVerified) async {
          log(
            VerifyPhoneNumberScreen.id,
            msg: autoVerified
                ? 'OTP was fetched automatically!'
                : 'OTP was verified manually!',
          );

          showSnackBar('Phone number verified successfully!');

          log(
            VerifyPhoneNumberScreen.id,
            msg: 'Login Success UID: ${userCredential.user?.uid}',
          );

          Navigator.pushNamedAndRemoveUntil(
            context,
            HomeScreen.id,
            (route) => false,
          );
        },
        onLoginFailed: (authException, stackTrace) {
          log(
            VerifyPhoneNumberScreen.id,
            msg: authException.message,
            error: authException,
            stackTrace: stackTrace,
          );

          switch (authException.code) {
            case 'invalid-phone-number':
              // invalid phone number
              return showSnackBar('Invalid phone number!');
            case 'invalid-verification-code':
              // invalid otp entered
              return showSnackBar('The entered OTP is invalid!');
            // handle other error codes
            default:
              showSnackBar('Something went wrong!');
            // handle error further if needed
          }
        },
        onError: (error, stackTrace) {
          log(
            VerifyPhoneNumberScreen.id,
            error: error,
            stackTrace: stackTrace,
          );

          showSnackBar('An error occurred!');
        },
        builder: (context, controller) {
          return Scaffold(
            appBar: AppBar(
              leadingWidth: 0,
              leading: const SizedBox.shrink(),
              title: const Text('Verify Phone Number'),
              actions: [
                if (controller.codeSent)
                  TextButton(
                    onPressed: controller.isOtpExpired
                        ? () async {
                            log(VerifyPhoneNumberScreen.id, msg: 'Resend OTP');
                            await controller.sendOTP();
                          }
                        : null,
                    child: Text(
                      controller.isOtpExpired
                          ? 'Resend'
                          : '${controller.otpExpirationTimeLeft.inSeconds}s',
                      style: const TextStyle(color: Colors.blue, fontSize: 18),
                    ),
                  ),
                const SizedBox(width: 5),
              ],
            ),
            body: controller.isSendingCode
                ? Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: const [
                      CustomLoader(),
                      SizedBox(height: 50),
                      Center(
                        child: Text(
                          'Sending OTP',
                          style: TextStyle(fontSize: 25),
                        ),
                      ),
                    ],
                  )
                : ListView(
                    padding: const EdgeInsets.all(20),
                    controller: scrollController,
                    children: [
                      Text(
                        "We've sent an SMS with a verification code to ${widget.phoneNumber}",
                        style: const TextStyle(fontSize: 25),
                      ),
                      const SizedBox(height: 10),
                      const Divider(),
                      if (controller.isListeningForOtpAutoRetrieve)
                        Column(
                          children: const [
                            CustomLoader(),
                            SizedBox(height: 50),
                            Text(
                              'Listening for OTP',
                              textAlign: TextAlign.center,
                              style: TextStyle(
                                fontSize: 25,
                                fontWeight: FontWeight.w600,
                              ),
                            ),
                            SizedBox(height: 15),
                            Divider(),
                            Text('OR', textAlign: TextAlign.center),
                            Divider(),
                          ],
                        ),
                      const SizedBox(height: 15),
                      const Text(
                        'Enter OTP',
                        style: TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.w600,
                        ),
                      ),
                      const SizedBox(height: 15),
                      PinInputField(
                        length: 6,
                        onFocusChange: (hasFocus) async {
                          if (hasFocus) await _scrollToBottomOnKeyboardOpen();
                        },
                        onSubmit: (enteredOtp) async {
                          final verified =
                              await controller.verifyOtp(enteredOtp);
                          if (verified) {
                            // number verify success
                            // will call onLoginSuccess handler
                          } else {
                            // phone verification failed
                            // will call onLoginFailed or onError callbacks with the error
                          }
                        },
                      ),
                    ],
                  ),
          );
        },
      ),
    );
  }
}

See the example directory for a complete sample app.

Created & Maintained By Rithik Bhandari

firebase_phone_auth_handler's People

Contributors

rithik-dev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

firebase_phone_auth_handler's Issues

Enable Android Device Verification

I followed your instructions but Android Device Verification is not found in Google Cloud instead of Google Play Integrity. What should I do please? Thanks.

UI glitch in OTP screen

After the OTP is sent, there is a UI glitch in VerifyPhoneNumberScreen. Could you please guide me to solve the issue?

Using the example code.

environment:
  sdk: '>=3.1.5 <4.0.0'

firebase_phone_auth_handler: ^1.0.8

Simulator: Pixel 7 API 33

@rithik-dev

web compatibility

Hey , thanks alot for the package.

you have push the latest release for linking in the phone auth with current user details. so as far as I'm aware, this method is not working yet on web. have you yet been able to achieve this on web ??

linking with a user signed in by username and password

im implimeting TFA where the user is signed in using email and password, then im sending a tesxt message to the sam user using this package, bu the problem is that this package creates a new reference which is not related to the user reference that is already created using email and password. can you please guid ? thanks

Question - Can I redirect user based on whether he is NewUser or not

Hi Rithik,

Thank you for this package. I am trying to explore more on this.

I am in need of checking if a user is logging in firstTime or he already has an account.

I would like him to fill up his name and other details if he is first time user. So is there a way I can find his status?

I did see this link https://firebase.google.com/docs/reference/android/com/google/firebase/auth/AdditionalUserInfo but could not apply it on my side.

Your help id much appreciated.

Thanks,
Raj

Question - ReAuthentication is it possible?

Hi @rithik-dev ,

I am using your package.

My current flow is as below.

User Singin/Signup using his phone number, if he is a new user I present him with the form to fill his personal details else navigate to home page.

As part of personal details I update email as well, so what this does is it send's a Signout signal, which logs out the user from the application, and when he is redirected to home page there is no more active user.

When I searched online it said in such cases we need to ReAuthenticate the user but there is no sample how to do it when it is a phone auth.

Do you have any suggestion?

Thanks

Disable Auto Send Code Verification

I'm making a new project using your package to verification OTP but when I navigate to new screen it auto send. that's not what i want. I want to send code when click button send. help please.

FlutterError: Looking up a deactivated widget's ancestor is unsafe.

When I push a new screen in onLoginSuccess, I'm getting the following exception:

Exception has occurred.
FlutterError (Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.)

This is my code:

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: FirebasePhoneAuthHandler(
        phoneNumber: widget.phoneNumber!,
        signOutOnSuccessfulVerification: false,
        linkWithExistingUser: false,
        autoRetrievalTimeOutDuration: const Duration(seconds: 60),
        otpExpirationDuration: const Duration(seconds: 60),
        onCodeSent: () {
          //showSnackbar("SMS-code verzonden");
        },
        onLoginSuccess: (userCredential, autoVerified) async {
          if (!loggingIn) {
            loggingIn = true;
            showSnackbar(autoVerified
                ? "SMS-code automatisch opgehaald"
                : "SMS-code geverifieerd");

            if (widget.isLoggingIn!) {
              types.User? currentUser =
                  await (fetchUser(userCredential.user!.phoneNumber));
              //print("Is bijlesgever: ${currentUser.isBijlesgever}");
              if (currentUser!.isBijlesgever!) {
                Navigator.pushReplacement(
                    ContextService.navigatorKey.currentContext!,
                    MaterialPageRoute(
                        builder: (context) => BijlesgeverMainScreen()));
                //await FlutterRestart.restartApp();
              } else {
                Navigator.pushReplacement(
                    ContextService.navigatorKey.currentContext!,
                    MaterialPageRoute(builder: (context) => MapScreen()));
                //await FlutterRestart.restartApp();
              }
            } else {
              Navigator.pushReplacement(
                  ContextService.navigatorKey.currentContext!,
                  MaterialPageRoute(
                      builder: (context) => AccountEmailScreen()));
            }
          }
        },
        ....
        ....

This is how the call stack looks like.
image

The error is coming from auth_handler.dart:

  @override
  void dispose() {
    try {
      FirebasePhoneAuthController._of(context, listen: false).clear();
    } catch (_) {}
    super.dispose();
  }

Update

Because firebase_phone_auth_handler >=1.0.6 depends on firebase_auth ^3.4.2 and App depends on firebase_auth ^4.0.2, firebase_phone_auth_handler >=1.0.6 is forbidden.
So, because App depends on firebase_phone_auth_handler ^1.0.6, version solving failed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.