Firebase provides authentication services that allow you to easily register and sign-in users. You can use email, passwords, phone numbers, and identity providers like Google and Facebook.

In this tutorial, you will learn how you can use Firebase Authentication in React to authenticate users using an email and password. You will store the user data collected in Firestore, a NoSQL cloud database also from Firebase.

Note that this tutorial uses Firebase v9 and React Router v6.

Create a Firebase Application

To connect your application to Firebase, register your app with Firebase to get a configuration object. This is what you will use to initialize Firebase in your React application.

To create a firebase application, follow the following steps.

  1. Head over to the Firebase console and click Create a project.
  2. Give your project a name and click create to start the process.
  3. Click on the Web icon (</>) on your project’s overview page to register the app.
  4. Give your app a name of your choice and click Register app. You don’t need to enable Firebase Hosting.
  5. Copy the configuration object under Add Firebase SDK.

Create a React Application

Use create-react-app to scaffold a React app.

        npx create-react-app react-auth-firebase

Navigate to the folder and start the application.

        cd react-auth-firebase
npm run start

Authenticate Users With Firebase Functions

Before using Firebase, install it.

        npm i firebase

Create a new file, firebase.js, and initialize Firebase.

        import { initializeApp } from "firebase/app";

const firebaseConfig = {
  apiKey: <api_key>,
  authDomain:<auth_domain> ,
  projectId: <project_id>,
  storageBucket: <storage_bucket>,
  messagingSenderId: <messaging_sender_id>,
  appId: <app_id>
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);

Use the configuration object you copied when you registered the app.

Next import the Firebase modules you will use.

        import {
  getAuth,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut,
} from "firebase/auth";
import { getFirestore, addDoc, collection } from "firebase/firestore";
const db = getFirestore();
const auth = getAuth();

To authenticate users, you need to create three functions: signUp, signIn, and signOut.

The signUp function passes the email and password to createUserWithEmailAndPassword to register a new user. createUserWithEmailAndPassword returns the user data which you will use to create a new user record in the database.

        const signUp = async (email, password) => {
  try {
    const userCredential = await createUserWithEmailAndPassword(
      auth,
      email,
      password
    );
    const user = userCredential.user;
    await addDoc(collection(db, "users"), {
      uid: user.uid,
      email: user.email,
    });
    return true
  } catch (error) {
    return {error: error.message}
  }
};

Note that you are not checking whether the email is already in use before registration because Firebase handles that for you.

Next, in the signIn function pass the email and password to the signInWithEmailAndPassword function to sign in a registered user.

        const signIn = async (email, password) => {
  try {
    const userCredential = await signInWithEmailAndPassword(
      auth,
      email,
      password
    );
    const user = userCredential.user;
    return true
  } catch (error) {
    return {error: error.message}
  }
};

Both the signUp and signOut functions return true if successful and an error message if an error occurs.

The signOut function is quite straightforward. It calls the signOut() function from Firebase.

        const signOut = async() => {
  try {
    await signOut(auth)
    return true
  } catch (error) {
    return false
  }
};

Create React Forms

The sign-in and sign-up forms will collect the email and password from the user.

Create a new component Signup.js and add the following.

        import { useState } from "react";
import { Link } from "react-router-dom";
import { signUp } from "./firebase";
const Signup = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, seterror] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (password !== password2) {
      seterror("Passwords do not match");
    } else {
      setEmail("");
      setPassword("");
      const res = await signUp(email, password);
      if (res.error) seterror(res.error)
    }
  };

  return (
    <>
      <h2>Sign Up</h2>
      <div>
        {error ? <div>{error}</div> : null}
        <form onSubmit={handleSubmit}>
          <input
            type="email"
            name="email"
            value={email}
            placeholder="Your Email"
            required
            onChange={(e) => setEmail(e.target.value)}
          />
          <input
            type="password"
            name="password"
            value={password}
            placeholder="Your Password"
            required
            onChange={(e) => setPassword(e.target.value)}
          />
          <button type="submit">Submit</button>
        </form>
        <p>
          already registered? <Link to="/login">Login</Link>
        </p>
      </div>
    </>
  );
};

export default Signup;

Here you are creating a signup form and keeping track of the email and password using state. Once you submit the form, the onSubmit event triggers the handleSubmit() function which calls the signUp() function from firebase.js. If the function returns an error, update the error state and display the error message.

For the sign-in form, create Signin.js and add the following.

        import { useState } from "react";
import { signIn } from "./firebase";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, seterror] = useState("");
  const handleSubmit = async (e) => {
    e.preventDefault();
    setEmail("");
    setPassword("");
    const res = await signIn(email, password);
    if (res.error) seterror(res.error);
  };
  return (
    <>
      {error ? <div>{error}</div> : null}
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          name="email"
          value={email}
          placeholder="Your Email"
          onChange={(e) => setEmail(e.target.value)}
        />
        <input
          type="password"
          name="password"
          value={password}
          placeholder="Your Password"
          onChange={(e) => setPassword(e.target.value)}
        />
        <input type="submit" value="submit" />
      </form>
    </>
  );
};

export default Login;

The sign-in form is quite similar to the signup page except that submission calls the signIn() function.

Lastly, create the Profile page. This is the page the app will redirect users to after successful authentication.

Create Profile.js and add the following.

        import { signOut } from "./firebase";

const Profile = () => {
  const handleLogout = async () => {
   await signOut();
  };
  return (
    <>
      <h1>Profile</h1>
<button onClick={handleLogout}>Logout</button>
    </>
  );
};

export default Profile;

In this component, you have the Profile heading and the logout button. The onClick handler on the button triggers the handleLogout function which signs out the user.

Create Authentication Routes

To serve the pages you created to the browser, set up react-router-dom.

Install react-router-dom:

        npm i react-router-dom

In index.js, configure react-router-dom:

        import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import App from "./App";
import Login from "./Login";
import Profile from "./Profile";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <AuthProvider>
        <Routes>
          <Route path="/" element={<App />} />
          <Route path="login" element={<Login />} />
          <Route path="profile" element={<Profile />} />
        </Routes>
      </AuthProvider>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

Up to this point, the application can register a user, sign them up and log them out. So how do you know whether a user is logged in or not?

In the next section of this tutorial, you will see how you can use React context to keep track of a user’s authentication status across the application.

Manage Authentication With React Context API

React Context is a state management tool that simplifies data sharing across apps. It is a better alternative to prop drilling, where data passes down the tree from parent to child until it reaches the component that needs it.

Create Authentication Context

In the src folder, add AuthContext.js file and create and export AuthContext.

        import { createContext } from "react";
const AuthContext = createContext();
export default AuthContext;

Next, create the provider in AuthProvider.js. It will allow components to use AuthContext.

        import { getAuth, onAuthStateChanged } from "firebase/auth";
import { useState, useEffect } from 'react';
import AuthContext from './AuthContext'
const auth = getAuth()
export const AuthProvider = ({ children }) => {
    const [user, setUser] = useState(null);
    useEffect(() => {
        onAuthStateChanged(auth,(user) => {
            setUser(user)
        })
    }, []);
  
    return (
      <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
    );
  };

Here, you are getting the user value by using the onAuthStateChanged() method from Firebase. This method returns a user object if it authenticates the user and null if it cannot. By using the useEffect() hook, the user value is updated each time the authentication status changes.

In index.js, wrap the routes with AuthProvider to ensure all the components access the user in the context:

        import { AuthProvider } from "./AuthProvider";
ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <AuthProvider>
        <Routes>
          <Route path="/" element={<App />} />
          <Route path="login" element={<Login />} />
          <Route path="profile" element={<Profile />} />
        </Routes>
      </AuthProvider>
    </BrowserRouter>
    ,
  </React.StrictMode>,
  document.getElementById("root")
);

Create Protected Routes

To protect sensitive routes, check the authentication status of a user trying to navigate to a protected page like the profile page.

Modify Profile.js to redirect a user if they are not authenticated.

        import { useContext } from "react";
import AuthContext from "./AuthContext";
import { useNavigate, Navigate } from "react-router-dom";
import { signOut } from "./firebase";

const Profile = () => {
  const { user } = useContext(AuthContext);
  const navigate = useNavigate();
  const handleLogout = async () => {
   await signOut();

  };

  if (!user) {
    return <Navigate replace to="/login" />;
  }
  return (
    <>
      <h1>Profile</h1>
<button onClick={handleLogout}>Logout</button>
    </>
  );
};

export default Profile;

The app conditionally renders the profile page by redirecting the user to the login page if they are not authenticated.

Going Further With Firebase Authentication

In this tutorial, you used Firebase to authenticate users using their email and password. You also created user records in Firestore. Firebase provides functions to work with authentication providers such as Google, Facebook, and Twitter.