DEV Community

Cover image for Multi-Factor(MFA) Authentication in React JS using firebase
Hasnain01-hub
Hasnain01-hub

Posted on

 

Multi-Factor(MFA) Authentication in React JS using firebase

So, in July 2022 Firebase added a new segment in authentication: MFA(Multi-Factor Authentication) which adds one more layer to authenticate users.

What is MFA?
It is a secure Authentication method which is also known as two-step authentication, which first registers the user by taking his email id and password, and in the following step, the user will get the 6-digit OTP in his phone no. After passing this step user will be authenticated and redirected to the home page.
Steps to integrate MFA in your React JS app:

1) First create your react app.

npx create-react-app mfaexample
Enter fullscreen mode Exit fullscreen mode

2) Create a new firebase project and configure your firebase project with your react app.

3) In order to have two-step authentication, first enable any authentication provider from authentication>Sign-in-method
In this blog, I will cover sign-in with email and password option as the first authentication layer.

4) Enable the MFA option under authentication>Sign-in-method in the below Advanced section of your firebase project.

Firebase setup Image

5) Create a Register page in which the first step is to create users using sign-in with email and password. The email needs to be verified for setting the 2-factor auth. After doing this a verification link will be sent to the email address of the newly created user. Then click on the share link the email will be verified the user data will be encrypted and stored in the local storage and will be redirected to the OTP page.
Also check the spam folder of your email address to get the verification link.

Register.js

import Cryptr from "cryptr";
const cryptr = new Cryptr("myTotallySecretKey");
const registerWithEmailAndPassword = async (e) => {
    e.preventDefault();
    try {
    //Form fields validation
      if (email !== "" && password !== "" && phone !== "") {
        if (phone.length < 10)
          return alert("Please enter a valid phone number");
         // Creating a new user with the email and password
        const res = await auth.createUserWithEmailAndPassword(email, password);
        const user = res.user;
        //encrypt the user data 
        const encryptedphone = cryptr.encrypt(`+91${phone}`);
        const encryptedpassword = cryptr.encrypt(password);

        const userdata = {
          email: email,
          password: encryptedpassword,
          phone: encryptedphone,
          user: user,
        };
        //Sending an verification link to user's email id
        await user
          .sendEmailVerification({ url: "http://localhost:3000/otp" })
          .then(async () => {
         //Store the users details in localStorage
            window.localStorage.setItem("user", JSON.stringify(userdata));
            setEmail("");
            setPassword("");
            setPhone("");
            alert("Email sent");
          });
      } else {
        alert("Please fill all the fields");
      }
    } catch (err) {
      console.error(err);
   }
  };
Enter fullscreen mode Exit fullscreen mode

6) The user data will be fetched from local storage and, 6-digit OTP will be sent to the registered phone number.

OtpScreen.js

import {
  multiFactor,
  PhoneAuthProvider,
  PhoneMultiFactorGenerator,
  RecaptchaVerifier,
} from "firebase/auth";
import Cryptr from "cryptr";
React.useEffect(() => {
  //get the user data from localStorage
    const getdata = JSON.parse(window.localStorage.getItem("user"));
   //if the data is null than redirect back to registerd page
    if (getdata === null) {
      history.push("/register");
    }
    sentotp(getdata);
  }, []);
const sentotp = async (getdata) => {
  /* A listener that triggers whenever the authentication state changes. */
    auth.onAuthStateChanged(async (user) => {
/* Decrypting the phone number that was encrypted in the previous step. */
      const decryptedphone = cryptr.decrypt(getdata.phone);
  /* Creating a new recaptcha verifier. */
      const recaptchaVerifier = new RecaptchaVerifier(
        "2fa-captcha",
        { size: "invisible" },
        auth
      );
      recaptchaVerifier.render();
      await multiFactor(user)
        .getSession()
        .then(function (multiFactorSession) {
          // Specify the phone number and pass the MFA session.
          const phoneInfoOptions = {
            phoneNumber: decryptedphone,
            session: multiFactorSession,
          };

          const phoneAuthProvider = new PhoneAuthProvider(auth);

          // Send SMS verification code.
          return phoneAuthProvider.verifyPhoneNumber(
            phoneInfoOptions,
            recaptchaVerifier
          );
        })
        .then(function (verificationId) {
          setverification(verificationId);
        });
    });
  };
Enter fullscreen mode Exit fullscreen mode

7) Add this function in OtpScreen.js, which will trigger after submitting the OTP. After the OTP is verified it will Enroll the user in the multi-factor authentication and remove the user data from the local storage.

verifyotp function

const verifyotp = async (e) => {
    e.preventDefault();
    try {
//get the OTP from user nad pass in PhoneAuthProvider
const cred = PhoneAuthProvider.credential(verification, otp);
const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);

      const user = auth.currentUser;
/* Enrolling the user in the multi-factor authentication. */
      await user.multiFactor
        .enroll(multiFactorAssertion, "phone number")
        .then((enrollment) => {
          history.push("/home");
        });
/* Removing the user from localStorage. */
      localStorage.removeItem("user");
    } catch (err) {
      alert("Invalid OTP");
      console.log(err);
    }
  };
Enter fullscreen mode Exit fullscreen mode

hurry! the signup part is completed.

Login Part

8) For the login part, pass the user credentials in signInWithEmailAndPassword function and store a boolean after successful login in local storage.

Login.js

const handleSubmit = async (e) => {
    e.preventDefault();
    try {
/* function that is provided by firebase to sign in a user with email and password. */
      await auth.signInWithEmailAndPassword(email, password);
    } catch (error) {
      if (error.code === "auth/multi-factor-auth-required") {
        // The user is enrolled in MFA, must be verified
        window.resolver = error.resolver;
      } else if (error.code === "auth/too-many-requests") {
        window.resolver = error.resolver;
      } else {
        console.log(error.code, error.message);
        alert("Invalid Credentials");
        return;
      }
    }
// store a true boolean after successfully login 
    window.localStorage.setItem("approvedsignin", JSON.stringify(true));
    history.push("/loginotp");
  };
Enter fullscreen mode Exit fullscreen mode

9) Fetch the data from local storage to check if the user has already completed the initial login step, 6-digit OTP will be sent to the registered phone number. Then pass the user entered OTP in PhoneAuthProvider function to verify it.

VerifyUser.js

import {
  PhoneAuthProvider,
  PhoneMultiFactorGenerator,
  RecaptchaVerifier,
} from "firebase/auth";
React.useEffect(() => {
  //check if the user has completed the first login step 
    const verfifer = JSON.parse(window.localStorage.getItem("approvedsignin"));
    if (verfifer === true) {
      sendOtp();
    } else {
      history.push("/");
    }
  }, []);

const sendOtp = async () => {
    const recaptchaVerifier = new RecaptchaVerifier(
      "2fa-captcha",
      { size: "invisible" },
      auth
    );
    recaptchaVerifier.render();
    const phoneOpts = {
      multiFactorHint: window.resolver.hints[0],
      session: window.resolver.session,
    };

    const phoneAuthProvider = new PhoneAuthProvider(auth);

    await phoneAuthProvider
      .verifyPhoneNumber(phoneOpts, recaptchaVerifier)
      .then((verificationId) => {
        setverification(verificationId);
      });
  };

  //verify otp 
const verify = async (e) => {
    e.preventDefault();
    try {
      //pass the user endtered otp
      const cred = PhoneAuthProvider.credential(verification, otp);

      const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);

      const credential = await window.resolver
        .resolveSignIn(multiFactorAssertion)
        .then((enrollment) => {
          auth.onAuthStateChanged(async (users) => {
            if (users) {
              //Remove boolean from localStorage
              window.localStorage.removeItem("approvedsignin");
              history.push("/home");
            }
          });
        });
    } catch (err) {
      alert("Invalid OTP");
      console.log(err);
    }
  };
Enter fullscreen mode Exit fullscreen mode

Note: Add this code in OtpScreen.js and VerifyUser.js file in JSX part

<div id="2fa-captcha" style={{ display: "flex", justifyContent: "center" }}></div>
Enter fullscreen mode Exit fullscreen mode

Congratulations, the two-factor authentication is set up for your react js app 😎.

Connect Me!
Linkedin: linkedin.com/in/hasnain-sayyed-537164177/
GitHub: https://github.com/Hasnain01-hub
Source Code: https://github.com/Hasnain01-hub/MultiFactorAuthentication-React

Buy me a Coffee: https://www.buymeacoffee.com/hasnainsayyed

If this post finds you helpful, plz like and follow me for more such amazing upcoming content 😊

Top comments (0)

11 Tips That Make You a Better Typescript Programmer

typescript

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!