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.
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.
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:
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.
Create a new React project using your desired application name. This guide uses passwordless-auth
.
npx create-react-app passwordless-auth
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
firebase.js
from the Prerequisite ProjectIn 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.
The React application will consist of 5 components: App
, Layout
, Login
, Confirm
, and Profile
.
App
App
component defines the overall application structure, including routing.Layout
Layout
component specifies the application markup that remains consistent across all routes.Login
Confirm
Confirm
page when they click on a sign-in link.Profile
Profile
page after confirming their email address.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
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 rebuild index.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 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
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.
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 the src
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>
)
}
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
.
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 the src
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
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.
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 the src
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
Profile.jsx
.Start the React application:
npm start
locahost:3000
in your browser if your browser does not launch automatically. You should see the Login
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.
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.
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.
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.
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.
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.