paint-brush
How To Set Up Firebase Passwordless Authentication With A React Web Applicationby@pictureinthenoise
2,005 reads
2,005 reads

How To Set Up Firebase Passwordless Authentication With A React Web Application

by Picture in the NoiseSeptember 24th, 2023
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

Passwordless authentication is an attractive option when building applications. It simplifies the user experience and developer experience. This tutorial walks through the steps to implement Firebase passwordless authentication with a simple React web application.
featured image - How To Set Up Firebase Passwordless Authentication With A React Web Application
Picture in the Noise HackerNoon profile picture

Introduction

This guide is a supplement to How to Set Up Firebase Authentication with a React Web Application. In the introduction of that guide, I noted that Firebase offers other authentication schemes beyond basic Email/Password authentication. One of those alternatives is Passwordless Authentication.


Passwordless authentication is an attractive option when building applications. It simplifies the user experience since your users have no need to remember their password(s) and thus never have to worry about losing them either. It also facilitates the development experience since there is no need to design any password capture or management logic.


In this guide, you will build a simple Login/Confirm/Profile/Logout workflow that implements Firebase's passwordless authentication.

Before You Begin

Google sets various Firebase Authentication limits. If you are using the free Spark plan, note that you will be limited to 5 sign-in link emails per day. While the Spark plan may be sufficient for testing purposes, you will need to upgrade to the pay-as-you-go Blaze plan to go above this limit.


Prerequisites

Throughout this guide, I will refer to the How to Set Up Firebase Authentication with a React Web Application guide as the prerequisite guide and the associated project as the prerequisite project.

To complete this guide, you will need to have:


  • Completed the prerequisite guide, including all of its prerequisites.


Step 1 - Enabling Passwordless Authentication With Your Firebase Project

In the prerequisite guide, you created a new Firebase project for basic email/password authentication. Now, you will enable that same project for passwordless authentication. Login to your Firebase account and click on Go to Console.


  1. Click on your authentication project listed on the Firebase projects dashboard. This guide uses the project name my-auth-test.

  2. Click on authentication in the left panel menu.

  3. Click on the Sign-in method tab in the main window.

  4. From your work in the prerequisite guide, the Sign-in Providers table should already display Email/Password under the Providers column with a status of Enabled. Click on the pencil icon to open the configuration panel for the Email/Password provider.

  5. Click on the toggle to enable the Email link (passwordless sign-in).

  6. Click Save.


Your Firebase project is now configured to support passwordless authentication.

Step 2 - Creating a New React Project and Installing Packages

Step 2a - Creating a New React Project

Create a new React project using your desired application name. This guide uses passwordless-auth.


npx create-react-app passwordless-auth


Step 2b - Installing Packages

This guide requires the installation of 3 Node.js packages:



Install each of the three packages above via npm:


npm install firebase


npm install react-router-dom


npm install bootstrap


Step 3 - Copy firebase.js from the Prerequisite Project

In the prerequisite project, you created a firebase.js file that uses your Firebase project configuration data to create an instance of the Firebase Authentication service. Your firebase.js file should have the following structure with your Firebase project configuration values:


import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";

const firebaseConfig = {
    apiKey: "AIzaSyDiUlY68W9Li_0EIkmdGdzD7nvqCT9kHnY",
    authDomain: "my-auth-test-fbd48.firebaseapp.com",
    projectId: "my-auth-test-fbd48",
    storageBucket: "my-auth-test-fbd48.appspot.com",
    messagingSenderId: "1078604952662",
    appId: "1:1078604952662:web:5d0b908439cfb5684ab7f7"
}

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

export { auth }


Copy firebase.js to your new React project folder.


If you need to review your Firebase project configuration, click on the gear icon next to Project Overview in the left panel menu. The General tab should already be selected. Scroll down to the Your Apps section containing the Web Apps panel. The npm option within the Web apps panel should already be selected. Your project configuration values will be listed in the displayed code block.


Step 4 - Building the React Application

Step 4a - Overview of React Application Components

The React application will consist of 5 components: App, Layout, Login, Confirm, and Profile.

App


  • The App component defines the overall application structure, including routing.

Layout

  • The Layout component specifies the application markup that remains consistent across all routes.

Login

  • The user's entry point into the application is the login form.
  • When a user attempts to login via his email address, an email with a sign-in link is sent to his/her email address.

Confirm

  • The user is routed to the Confirm page when they click on a sign-in link.
  • The page requests the user to confirm the email address used when they logged in.

Profile

  • The user is routed to the Profile page after confirming their email address.
  • The user can click a logout button on the Profile page to logout of his/her account.

The final src directory will contain the following files:


src
|__ index.js
|__ firebase.js // Copied from prerequisite project in Step 3.
|__ App.js
|__ Layout.jsx
|__ Login.jsx
|__ Confirm.jsx
|__ Profile.jsx


Step 4b - Cleaning Up the React Project Template and Copying Files from the Prerequisite Project

  1. You can delete the same files from the React project template as described in Step 5b.1 of the prerequisite guide. Delete the following files from your React project:
  • reportWebVitals.js
  • setupTests.js
  • logo.svg
  • index.css
  • App.css
  • App.test.js
  1. Copy index.js from the prerequisite project to your new project folder. This guide uses the same file. If you need to rebuild index.js, see Step 5b.2 of the prerequisite guide or copy the code block below.

  2. Copy Layout.jsx from the prerequisite project to your new project folder. This guide uses the same file. If you need to rebuild Layout.jsx, see Step 5d of the prerequisite guide or copy the code block below. Optionally, you may update the project text in the <p> tag of Layout.jsx to React With Firebase Passwordless Authentication or whatever title you may prefer.


Your index.js and Layout.jsx files should be as follows:


// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import "bootstrap/dist/css/bootstrap.min.css";

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);


// Layout.jsx
import { Outlet } from "react-router-dom";

const Layout = () => {
    return(
        <div className = "container-fluid">
            <div className = "row justify-content-center mt-3">
                <div className = "col-md-4 text-center">
                    <p className = "lead">React With Firebase Passwordless Authentication</p>
                </div>
                <Outlet />
            </div>
        </div>
    )
}

export default Layout


Step 4c - Building App.js

The App.js file is largely the same as it is in the prerequisite project, with changes to only two lines. To facilitate building the file, copy App.js from the prerequisite project to your new project folder.

  1. Delete the following import line from the file and replace it as shown below:


// Delete this line:
import Signup from "./Signup";


// Replace it with:
import Confirm from "./Confirm";


  1. Delete the following <Route> from the file and replace it as shown below:


// Delete this line:
<Route path = "/signup" element = { <Signup></Signup> } ></Route>


// Replace it with:
<Route path = "/confirm" element = { <Confirm></Confirm> } ></Route>


  1. Save App.js.


The complete file should now be as follows:


import Layout from "./Layout";
import Login from "./Login";
import Confirm from "./Confirm";
import Profile from "./Profile";
import { BrowserRouter, Routes, Route } from "react-router-dom";

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
          <Route path = "/" element = { <Layout></Layout> }>
            <Route index element = { <Login></Login> }></Route>
            <Route path = "/confirm" element = { <Confirm></Confirm> } ></Route>
            <Route path = "/profile" element = { <Profile></Profile> } ></Route>
          </Route>
      </Routes>
    </BrowserRouter>
  )
}

export default App


Note that the Login component is once again the home route of the application as it was with the prerequisite project.


Step 4d - Building Login.jsx

In the case of passwordless authentication, you obviously don't need to include a password field, nor do you need to manage state for password input. So, the login form only needs to capture the user's email address.


  1. Create a new Login.jsx file in the src directory.

  2. Add the following code:


import { useState } from "react";
import { auth } from "./firebase";
import { sendSignInLinkToEmail } from "firebase/auth";

const Login = () => {
    const [email, setEmail] = useState("");
    const [notice, setNotice] = useState("");

    const actionCodeSettings = {
        url: "http://localhost:3000/confirm",
        handleCodeInApp: true
    }

    const callSendSignInLinkToEmail = (e) => {
        e.preventDefault();

        sendSignInLinkToEmail(auth, email, actionCodeSettings)
        .then(() => {
            setNotice("An email was sent to your email address. Click the link in the email to login.");
        })
        .catch((error) => {
            setNotice("An error occurred when sending a login link to your email address: ", error.name);
        })
    }

    return(
        <div className = "container">
            <div className = "row justify-content-center">
                <form className = "col-md-4 mt-3 pt-3 pb-3">
                    { "" !== notice &&
                        <div className = "alert alert-warning" role = "alert">
                            { notice }    
                        </div>
                    }                  
                    <div className = "form-floating mb-3">
                        <input type = "email" className = "form-control" id = "exampleInputEmail" placeholder = "[email protected]" value = { email } onChange = { (e) => setEmail(e.target.value) }></input>
                        <label htmlFor = "exampleInputEmail" className = "form-label">Email address</label>
                    </div>
                    <div className = "d-grid">
                        <button type = "submit" className = "btn btn-primary pt-3 pb-3" onClick = {(e) => callSendSignInLinkToEmail(e)}>Submit</button>
                    </div>
                </form>
            </div>
        </div>
    )
}
  1. Save Login.jsx.


Upon capturing the user's email address, the Login.jsx form sends an email with a sign-in link to his/her address via Firebase's sendSignInLinkToEmail method. If successful, the user is notified that the email has been sent. Note that the actionCodeSettings object is passed as a parameter to the sendSignInLinkToEmail method and includes the URL that the user will be routed to when he/she clicks on the emailed sign-in link. In this case, the URL maps to the /confirm route specified in App.js.


Step 4e - Building Confirm.jsx

Firebase's signInWithEmailLink method is used to sign in a user who has clicked on a sign-in link. As you will see in a moment, the method takes an email parameter, and the value of email must match the email address that the user used when logging in via the login form. Confirm.jsx presents the user with a form to confirm his/her email address and subsequently attempts to sign in the user.


  1. Create a new Confirm.jsx file in the src directory.

  2. Add the following code:


import { useState } from "react";
import { auth } from "./firebase";
import { isSignInWithEmailLink, signInWithEmailLink } from "firebase/auth";
import { useNavigate } from "react-router-dom";

const Confirm = () => {
    const navigate = useNavigate();
    const [email, setEmail] = useState("");
    const [notice, setNotice] = useState("");

    const callSignInWithEmailLink = (e) => {
        e.preventDefault();

        if (isSignInWithEmailLink(auth, window.location.href)) {
            signInWithEmailLink(auth, email, window.location.href)
            .then(() => {
                navigate("/profile");
            })
            .catch((error) => {
                setNotice("An occurred during sign in: ", error.name);
            })
        }
    }

    return(
        <div className = "container">
            <div className = "row justify-content-center">
                <form className = "col-md-4 mt-3 pt-3 pb-3">
                    { "" !== notice &&
                        <div className = "alert alert-warning" role = "alert">
                            { notice }    
                        </div>
                    }                  
                    <div className = "form-floating mb-3">
                        <input type = "email" className = "form-control" id = "exampleConfirmEmail" placeholder = "[email protected]" value = { email } onChange = { (e) => setEmail(e.target.value) }></input>
                        <label htmlFor = "exampleConfirmEmail" className = "form-label">Please confirm your email address</label>
                    </div>
                    <div className = "d-grid">
                        <button type = "submit" className = "btn btn-primary pt-3 pb-3" onClick = {(e) => callSignInWithEmailLink(e)}>Confirm</button>
                    </div>
                </form>
            </div>
        </div>
    )
}

export default Confirm
  1. Save Confirm.jsx.


The isSignInWithEmailLink method first checks if the sign-in link that the user is using is valid. If it is, the signInWithEmailLink method is called to sign in the user. To reiterate, the email value passed to the signInWithEmailLink method must match the email address the user used with the login form. Note that if the user is a new user (i.e., it is his/her first time signing in), Firebase will automatically create the user in the Firebase Authentication store. This is another example of the simplified experience offered by passwordless authentication: account creation for new users is handled automatically.

Step 4f - Building Profile.jsx

The final component that you will build is Profile.jsx. Users are routed to this component when they sign in successfully via Confirm.jsx. The route welcomes the user with their email address and provides a button to logout. Upon logout, the user is routed back to the Login component.

  1. Create a new Profile.jsx file in the src directory.

  2. Add the following code:


import { auth } from "./firebase";
import { signOut } from "firebase/auth";
import { useNavigate } from "react-router-dom";

const Profile = () => {
    const navigate = useNavigate();

    const logoutUser = async (e) => {
        e.preventDefault();

        await signOut(auth);
        navigate("/");
    }

    return(
        <div className = "container">
            <div className = "row justify-content-center">
                <div className = "col-md-4 text-center">
                    <p>Welcome <em className = "text-decoration-underline">{ auth.currentUser.email }</em>. You are logged in!</p>
                    <div className = "d-grid gap-2">
                        <button type = "submit" className = "btn btn-primary pt-3 pb-3" onClick = {(e) => logoutUser(e)}>Logout</button>
                    </div>                
                </div>
            </div>
        </div>       
    )    
}

export default Profile
  1. Save Profile.jsx.

Step 5 - Testing the Application

  1. Start the React application:


npm start


  1. Navigate to locahost:3000 in your browser if your browser does not launch automatically. You should see the Login form.

Login form.


  1. Enter the email you would like to use to sign in and click Submit. If the submission is successful, a notification will be displayed that an email with a sign-in link was sent to your email address.


Entering email address on Login form.



Email sign-in link sent notification.


  1. Login to your email account and look for the Firebase sign-in link email. It should have a subject line similar to Sign in to project-1078604952662 where the 13-digit number sequence represents the messagingSenderId of your Firebase project (see Step 3 of this guide). In the Optional section below, I will explain how you can modify your Firebase project name to display a "user-friendly" name in sign-in link emails. For now, open the sign-in link email and click on the sign-in link. You will be routed to the Confirm form.

Sample Firebase sign-in link email.


  1. Enter the email address you used when signing in on the Confirm form. Click Confirm. If the confirmation is successful, you will be routed to the Profile page.


Email address confirmation form.



Profile page following successful sign in.


  1. Click the Logout button on the Profile page to sign out. If the sign-out is successful, you will be routed back to the Login form.


The steps above capture the workflow for the application.

Optional: Modifying Your Project Name

You can change your project name so that sign-in link emails sent by Firebase display a "user-friendly" name instead of, for example, project-1078604952662. Login to your Firebase account and click on Go to Console.


  1. Click on your authentication project listed on the Firebase projects dashboard.
  2. Click on the gear icon next to Project Overview in the left panel menu. The General tab should already be selected.
  3. Scroll down to the Public-facing name option in the Your Project section.
  4. Click on the pencil icon and modify your project name as desired.
  5. Click Save. Your sign-in link emails will now display the updated project name.

Conclusion and Next Steps

Passwordless authentication seems to be an increasingly popular choice among application developers, and understandably so. Beyond the obvious advantage of eliminating the need to manage passwords, there is also no need for email verification since the process of sending the sign-in link is a verification in and of itself.


As with the prerequisite project, the implementation here is basic. You might consider simple enhancements such as:


  • Blocking/blacklisting particular email address domains (i.e., common spam email domains).

  • Locally storing the email address entered by the user on the Login page and checking for the existence of the email address on the Confirm page. With this approach, if the user clicks on a sign-in link on the same device where he/she accessed the Login page, he/she would not need to enter his/her email address again on the Confirm page as it would be recovered from local storage. This provides for an even more frictionless user experience.


You can learn more about Firebase Passwordless Authentication via the official documentation.