Authentication in React 18 using Firebase v9
Tags: javascript, webdev, react, firebase
Published: 2022-05-21 21:01:00

by [Wisdom Ekpotu](https://blog.openreplay.com/authors/wisdom-ekpotu)

To identify users of an application, it is critical to create a user authentication mechanism. On a backend server, this process might become quite complicated at times. This is when Firebase enters the picture. [Firebase](https://firebase.google.com/) has made it incredibly simple to create a complete authentication infrastructure without having to worry about security, scaling, or maintenance, among other things.

In this article, we will implement an authentication flow in [React 18](https://reactjs.org/) using [Firebase v9](https://firebase.google.com/) Web SDK. We'll enhance a starter React application to practice the following security concepts: Adding user login and logout (using email/password and with Google), Retrieving user information, using the Context API for managing the Auth state, and Protecting application routes from unauthorized users using [React Router v6](https://reactrouter.com/).

Before you begin this tutorial, you'll need the following:

- Prior Knowledge of React.
- Code Editor — such as [VSCode](https://code.visualstudio.com/).
- A Google Account.
- [Node.js Latest LTS](https://nodejs.org/en/download/) installed on your computer.

Ok, let's jump right in.

## Create a Firebase Project

After ensuring you are logged in with your Google account, go to the [Firebase Console](https://console.firebase.google.com/). Click **add Project** and give your project a name, such as "React18-Firebasev9-Auth" in my case. Then click **Continue**. We won't be using Google Analytics in this case, so we'll skip that step.

You will then be redirected to the **Dashboard**  which should look like this:
 
![Dashboard](https://blog.openreplay.com/static/b373d4b58e8f5a5fcab425d279db9da7/b71f8/image01.png)

Go to "authentication", Click on **Get Started**, and navigate to **Sign-in method**. Here, you will see various available methods, but we will be implementing just two  (**Email/Password** and **Google**) for this tutorial. Click on **Email/Password**,  Enable and then save.
  
![Email/Password method](https://blog.openreplay.com/static/af24ca524b1f995cd4d1e18a83a7cb6e/68382/image02.png)
 
Then, for **Google**, we repeat the process. Enable it, add the Project support email, and save it.
 
![Google method](https://blog.openreplay.com/static/e37485c8350942d86e22e764c7e7e1db/68382/image03.png)

![Authentication](https://blog.openreplay.com/static/9630f843e64c52a34652880d7fcf16e5/68382/image04.png)


## Get the Starter React 18 Application 

For the purpose of this tutorial, I have created a [starter project](https://github.com/wisdomekpotu/react18-firebasev9-auth/tree/starter) using `create-react-app` to enable us to focus more on authentication. The starter application uses [React Bootstrap](https://react-bootstrap.github.io/) to manage styling and layout. To get started, clone the `react18-firebasev9-auth` repository on the `starter` branch:
  
```
git clone -b starter https://github.com/wisdomekpotu/react18-firebasev9-auth.git
```
 
Once you clone the repo, make `react18-firebasev9-auth` your current directory:
  
```
cd react18-firebasev9-auth
```
 
Install the React project dependencies:
 
```
yarn install 
```

Start the application: 
 
```
yarn start
``` 

##  Adding Firebase to our React app

To add Firebase to our React application, we must first register it on the [Firebase Console](https://console.firebase.google.com/). Click on the **3rd** icon, which is for Web Applications.  

![Adding Firebase to our app](https://blog.openreplay.com/static/5a555911cd4a5d9ac23fd7e5e6415af6/d82cd/image05.png)

Then add the application name and click **Register app**.
 
![Registering the app](https://blog.openreplay.com/static/87c325bef4beb06eea4db9a27aaee6bd/427c0/image06.png)

We don't need to set up Firebase hosting, so we'll skip it. You'll see your Firebase project credentials, which we'll need to set up Firebase.
 
![Secrets for our app](https://blog.openreplay.com/static/65e52e91a546c0ded73c800449788a87/a4922/image07.png)
 
The next step is to add Firebase to our project. To accomplish this, run:
 
```
yarn add firebase
```
 
Create a `firebase-config.js` file in the `src` folder. This is where we'll enter our credentials to connect to Firebase. According to the documentation for [create-react-app](https://create-react-app.dev/docs/adding-custom-environment-variables/),
- Do not store any secrets (such as private API keys) in your React app!. 
- You must create custom environment variables beginning with `REACT_APP_`.

As a result, we must save all sensitive information about our Firebase project in a `.env` file. Go to the root of your application and create a file called `.env`.
 
```
touch .env
```

Then populate the `.env` file as follows with the credentials generated from Firebase :
 
```
REACT_APP_FIREBASE_API_KEY = 
REACT_APP_FIREBASE_AUTH_DOMAIN = 
REACT_APP_FIREBASE_PROJECT_ID = 
REACT_APP_FIREBASE_STORAGE_BUCKET = 
REACT_APP_FIREBASE_MESSAGING_SENDER_ID = 
REACT_APP_FIREBASE_APP_ID = 
```

Include the `.env` file in the `.gitignore` file to prevent it from being pushed to our GitHub repo.
 
```
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
```
 
Add the following code to the `firebase-config` file:

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

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export default app;
```
 
With our credentials, we initialize Firebase in our app. To access our configuration credentials, we use the `process.env` just right in front of the variable names as declared in the `.env` file. Then we import the `getAuth` method from the Firebase auth's module and export both for use in our app. Let's go on and write the Firebase functions for authentication.

## Context API for Authentication

The [Context API](https://reactjs.org/docs/context.html) is a feature in React that will allow us to share state across the entire app (or part of it) lightly and with ease. Add the following code to the `UserAuthContext.js` file:
  
```javascript
import { createContext, useContext, useEffect, useState } from "react";
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  onAuthStateChanged,
  signOut
} from "firebase/auth";
import { auth } from "../firebase";

const userAuthContext = createContext();

export function UserAuthContextProvider({ children }) {
  const [user, setUser] = useState({});

  function logIn(email, password) {
    return signInWithEmailAndPassword(auth, email, password);
  }
  function signUp(email, password) {
    return createUserWithEmailAndPassword(auth, email, password);
  }
  function logOut() {
    return signOut(auth);
  }

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (currentuser) => {
      console.log("Auth", currentuser);
      setUser(currentuser);
    });

    return () => {
      unsubscribe();
    };
  }, []);

  return (
    <userAuthContext.Provider
      value={{ user, logIn, signUp, logOut }}
    >
      {children}
    </userAuthContext.Provider>
  );
}

export function useUserAuth() {
  return useContext(userAuthContext);
}
```

We first build a context provider called `UserAuthContextProvider` and then pass in the `children`. Then we import a couple of Firebase methods (`createUserWithEmailAndPassword` , `signInWithEmailAndPassword` , and `signOut`) from our Firebase auth module. These methods are used to develop functions for creating, logging in, and signing out users through email and password. We also import a method named `onAuthStateChanged`, which monitors authentication status changes and returns the current user. We construct a function called `useUserAuth` and wrap it in a custom hook called `useContext` to make this Context API available to our app.

## Signup Component Firebase Integration 

We've built the Firebase functions, but they're not yet available in our app. As a result, we must import them into our `App.js` file. Add this line of code to the `App.js` file.
 
```javascript
import { UserAuthContextProvider } from "./context/UserAuthContext";
```
 
Then wrap the routes with the provider. It should look like this.
 
```
 <UserAuthContextProvider>
      <Routes>
              <Route path="/home" element={<Home />} />
              <Route path="/" element={<Login />} />
              <Route path="/signup" element={<Signup />} />
     </Routes>
</UserAuthContextProvider>
```

Navigate to the `Signup.js` file and modify the code like this: 

```javascript
import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { Form, Alert } from "react-bootstrap";
import { Button } from "react-bootstrap";
import { useUserAuth } from "../context/UserAuthContext";

const Signup = () => {
  const [email, setEmail] = useState("");
  const [error, setError] = useState("");
  const [password, setPassword] = useState("");
  const { signUp } = useUserAuth();
  let navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError("");
    try {
      await signUp(email, password);
      navigate("/");
    } catch (err) {
      setError(err.message);
    }
  };

  return (
    <>
      <div className="p-4 box">
        <h2 className="mb-3">Firebase Auth Signup</h2>
        {error && <Alert variant="danger">{error}</Alert>}
        <Form onSubmit={handleSubmit}>
          <Form.Group className="mb-3" controlId="formBasicEmail">
            <Form.Control
              type="email"
              placeholder="Email address"
              onChange={(e) => setEmail(e.target.value)}
            />
          </Form.Group>

          <Form.Group className="mb-3" controlId="formBasicPassword">
            <Form.Control
              type="password"
              placeholder="Password"
              onChange={(e) => setPassword(e.target.value)}
            />
          </Form.Group>

          <div className="d-grid gap-2">
            <Button variant="primary" type="Submit">
              Sign up
            </Button>
          </div>
        </Form>
      </div>
      <div className="p-4 box mt-3 text-center">
        Already have an account? <Link to="/">Log In</Link>
      </div>
    </>
  );
};

export default Signup;
```
 
Basically, we import the `useUserAuth` method from `useUserContext`, and then we add `onChange` handlers to listen to changes in our input fields and get the values. We initialize the email and password states as empty and then assign these values as the new state values. We constructed a `handleSubmit` function that will use our `useUserAuth` method to call the `signUp` Firebase function and then sign in users only when the submit button is clicked, after which we will redirect to the `Login` component page using the `useNavigate` hook.
 

## Open Source Session Replay

_[OpenReplay](https://github.com/openreplay/openreplay) is an open-source alternative to FullStory and LogRocket. It gives you full observability by replaying everything your users do on your app and showing how your stack behaves for every issue. OpenReplay is self-hosted for full control over your data._

![replayer.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647097041609/Qi6sMOaaB.png)

_Happy debugging for modern frontend teams - [start monitoring your web app for free](https://github.com/openreplay/openreplay)._


## Login Component Firebase Integration 

Open `src/components/Login.js` and update it as follows: 

```javascript
import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { Form, Alert } from "react-bootstrap";
import { Button } from "react-bootstrap";
import GoogleButton from "react-google-button";
import { useUserAuth } from "../context/UserAuthContext";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const { logIn } = useUserAuth();
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError("");
    try {
      await logIn(email, password);
      navigate("/home");
    } catch (err) {
      setError(err.message);
    }
  };

  return (
    <>
      <div className="p-4 box">
        <h2 className="mb-3">Firebase Auth Login</h2>
        {error && <Alert variant="danger">{error}</Alert>}
        <Form onSubmit={handleSubmit}>
          <Form.Group className="mb-3" controlId="formBasicEmail">
            <Form.Control
              type="email"
              placeholder="Email address"
              onChange={(e) => setEmail(e.target.value)}
            />
          </Form.Group>

          <Form.Group className="mb-3" controlId="formBasicPassword">
            <Form.Control
              type="password"
              placeholder="Password"
              onChange={(e) => setPassword(e.target.value)}
            />
          </Form.Group>

          <div className="d-grid gap-2">
            <Button variant="primary" type="Submit">
              Log In
            </Button>
          </div>
        </Form>
        <hr />
        <div>
          <GoogleButton
            className="g-btn"
            type="dark"
          />
        </div>
      </div>
      <div className="p-4 box mt-3 text-center">
        Don't have an account? <Link to="/signup">Sign up</Link>
      </div>
    </>
  );
};

export default Login;
```

The code above is similar to what we did in `Signup.js`. We import the `useUserAuth` method from `UserAuthContext`, and then we set email and password states to empty. Add `onChange` listeners to get the values from the inputs. Then we wrote a function that will call the `logIn` function passed down from `useUserAuth` to authenticate the user, and then we also make use of the `useNavigate` hook from "react-router-dom" to redirect to the home page.

## Adding Firebase Logout Functionality 

This is pretty simple to implement as all we need to do is call the `logOut` function. First, we go to the `Home.js` component, where we would find out logout button. We add an `Onclick` handler and then write a `handleLogout` function which calls the firebase `logOut` function gotten from the `useUserContext` imported. The code should look like this: 

```javascript
import { useNavigate } from "react-router";
import { useUserAuth } from "../context/UserAuthContext";

const Home = () => {
  const { logOut, user } = useUserAuth();
  const navigate = useNavigate();
  const handleLogout = async () => {
    try {
      await logOut();
      navigate("/");
    } catch (error) {
      console.log(error.message);
    }
  };

  return (
    <>
      <div className="p-4 box mt-3 text-center">
        Hello Welcome <br />
      </div>
      <div className="d-grid gap-2">
        <Button variant="primary" onClick={handleLogout}>
          Log out
        </Button>
      </div>
    </>
  );
};

export default Home;
```

## Sign In With Google Auth  

Navigate to the `Login` component. On the `GoogleButton` add an onClick handler `onClick={handleGoogleSignIn}`. Then create an asynchronous function `handleGoogleSignIn`, which will call the `googleSignIn` function from our context, authenticate the user, and redirect to the home page.

```javascript
const handleGoogleSignIn = async (e) => {
  e.preventDefault();
  try {
    await googleSignIn();
    navigate("/home");
  } catch (error) {
    console.log(error.message);
  }
};
```
 
Once you have successfully authenticated users using both email/password and Google. Go back to your [Firebase Console](https://console.firebase.google.com/) and notice that the user data has been saved.
 
![User data was saved](https://blog.openreplay.com/static/541774bb08b02dceda27535fb2a5dbb5/c7658/image08.png)

## Firebase Protected Route Setup 

[React Router v6](https://reactrouter.com/docs/en/v6) enables us to protect unauthorized/unauthenticated users from accessing sensitive routes. Since we do not want users having access to the home page when not logged in, so we need to make it protected. To start, create a `ProtectedRoute.js` file under the src/components directory:

```
touch src/components/ProtectedRoute.js
```

Populate src/auth/protected-route.js as follows:

```javascript
import React from "react";
import { Navigate } from "react-router-dom";
import { useUserAuth } from "../context/UserAuthContext";
const ProtectedRoute = ({ children }) => {
  const { user } = useUserAuth();

  console.log("Check user in Private: ", user);
  if (!user) {
    return <Navigate to="/" />;
  }
  return children;
};

export default ProtectedRoute;
```

We import the `useUserAuth` method from our Context API and the `useNavigate` hook from React Router in the code above. If the user is not authenticated, we redirect to the login page; otherwise, we make the home page available and redirect the user to it.

After that, we must export it to use it in our `App.js` file. Go to `App.js` and make the necessary changes.

```javascript
import { Container, Row, Col } from "react-bootstrap";
import { Routes, Route } from "react-router-dom";
import "./App.css";
import Home from "./components/Home";
import Login from "./components/Login";
import Signup from "./components/Signup";
import ProtectedRoute from "./components/ProtectedRoute";
import { UserAuthContextProvider } from "./context/UserAuthContext";

function App() {
  return (
    <Container style={{ width: "400px" }}>
      <Row>
        <Col>
          <UserAuthContextProvider>
            <Routes>
              <Route
                path="/home"
                element={
                  <ProtectedRoute>
                    <Home />
                  </ProtectedRoute>
                }
              />
              <Route path="/" element={<Login />} />
              <Route path="/signup" element={<Signup />} />
            </Routes>
          </UserAuthContextProvider>
        </Col>
      </Row>
    </Container>
  );
}

export default App;
```

In the code above, we import the `ProtectRoute` and proceed to wrap the `Home` component with it: now our home route is protected, but the login and sign up routes are still available to everybody.

The code for this tutorial can be found here [GitHub Repo](https://github.com/wisdomekpotu/react18-firebasev9-auth).

## Conclusion
So far in this tutorial, we have used Firebase to authenticate users using their email/password and with a Google account. You can take this a step further by adding the "forgot password" functionality. You can also make use of Firestore to save users. And then you can also see how to implement sign-in with Twitter, Facebook, etc. -- plenty of options!

Some resources you may want to look at:

- [Firebase Official Documentation](https://firebase.google.com/docs/web/setup)
- [React Router v6](https://reactrouter.com/)
- [Context API](https://reactjs.org/docs/context.html)



[![newsletter](https://blog.openreplay.com/static/e6b18eda7a327511296a2f27692f9c70/e9957/newsletter.png)](https://newsletter.openreplay.com/)