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.
-
Click on your authentication project listed on the Firebase projects dashboard. This guide uses the project name
my-auth-test
. -
Click on authentication in the left panel menu.
-
Click on the Sign-in method tab in the main window.
-
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.
-
Click on the toggle to enable the Email link (passwordless sign-in).
-
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:
- Firebase: The Firebase SDK.
- React Router DOM: For routing.
- Bootstrap: For styling.
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
- 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
-
Copy
index.js
from the prerequisite project to your new project folder. This guide uses the same file. If you need to rebuildindex.js
, see Step 5b.2 of the prerequisite guide or copy the code block below. -
Copy
Layout.jsx
from the prerequisite project to your new project folder. This guide uses the same file. If you need to rebuildLayout.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 ofLayout.jsx
toReact 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.
-
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";
-
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>
-
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.
-
Create a new
Login.jsx
file in thesrc
directory. -
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>
)
}
- 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.
-
Create a new
Confirm.jsx
file in thesrc
directory. -
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
- 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.
-
Create a new
Profile.jsx
file in thesrc
directory. -
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
- Save
Profile.jsx
.
Step 5 - Testing the Application
-
Start the React application:
npm start
- Navigate to
locahost:3000
in your browser if your browser does not launch automatically. You should see theLogin
form.
-
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.
- 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 themessagingSenderId
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 theConfirm
form.
-
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 theProfile
page.
-
Click the Logout button on the
Profile
page to sign out. If the sign-out is successful, you will be routed back to theLogin
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.
- Click on your authentication project listed on the Firebase projects dashboard.
- 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 Public-facing name option in the Your Project section.
- Click on the pencil icon and modify your project name as desired.
- 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 theConfirm
page. With this approach, if the user clicks on a sign-in link on the same device where he/she accessed theLogin
page, he/she would not need to enter his/her email address again on theConfirm
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.