Ultimate Guide to React Firebase Authentication

React Firebase Authentication Cover

Authentication is one of the most important security measures of every application.

In this article, we’ll talk about authentication, authentication types, compare authentication to authorization and demonstrate how to integrate Firebase Authentication and Back4app Authentication with a React app.

What is Authentication?

Authentication is the process of verifying whether someone or something is what they claim to be. Almost every application uses some form of authentication to secure access to an application or its data.

The simplest form of authentication is password-based authentication. This authentication system starts with a user entering their username and password. After they’ve entered their credentials the backend compares them to the records in the database and grants them access to the system if the credentials were correct. The access is usually granted in form of session tokens.

A modern authentication system looks something like this:

Modern Authentication System Flow

Authentication vs. Authorization

Authentication is the process of verifying who a user is, while authorization is the process of verifying what a user has access to.

For example, when you log in to an online banking system the system will first authenticate you by verifying your login credentials. Once you have been authenticated, the system will determine what actions you are authorized to take based on your account privileges, such as whether you are allowed to transfer money or view account statements.

Authentication Types

The common authentication types include:

  • Password-based authentication is the most common authentication type. It involves a user providing their username and password to gain access to a system or a resource.
  • Single sign-on (SSO) allows a user to log into multiple websites and services using a single set of credentials. An example would be Google Accounts. By creating a Google Account you get access to Gmail, YouTube, AdSense, Firebase, and so on.
  • Social authentication is a form of single sign-on that uses existing information from a social networking service such as Google, Facebook, or Twitter to create an account.
  • Token-based authentication enables users to enter their credentials once and receive a unique encrypted string of random characters in exchange.
  • Biometric authentication relies on the unique biological characteristics of an individual. Types of biometric authentication include facial recognition, fingerprint scanners, speaker recognition, and iris scanners.
  • Certificate-based authentication uses digital certificates to verify the identity of a person, organization, or device to establish a secure connection for the exchange of information over the internet.

How to Setup Authentication?

In this part of the tutorial we’ll first build a dummy React authentication app and then look at how to integrate it with Firebase Authentication and Back4app (Parse) Authentication.

We’re going to be using the following tools:

Prerequisites:

  • Experience with JavaScript ES6
  • A decent understanding of React (JSX, Hooks)
  • Experience with React Router and Material UI is a plus

Before diving into the code let’s look at the differences between the authentication systems.

Firebase Authentication vs Back4app Authentication

Firebase as well as Back4app both provide great user authentication systems for mobile and web applications. They are both easy to use and come with a bunch of functionalities such as social login, email verification system, password reset system et cetera.

The biggest difference between them is that Back4app is built on open-source software while Firebase uses Google’s proprietary software. Additionally, Back4app tends to be cheaper than Firebase.

Firebase Authentication versus Back4app Authentication

I’d suggest you try both of the authentication systems and then decide which one you like better.

If you wish to explore the differences between Back4app and Firebase take a look at Back4app vs Firebase.

Project Setup

We’re done with the theory. It’s time to start coding!

Create React App

Let’s start by creating a new React app.

The following steps will require you to have Node.js installed. If you don’t have it installed yet, download it from their official website.

The easiest way to create a React app is via Create React App:

$ npx create-react-app react-firebase-auth
$ cd react-firebase-auth

This will create a new React app named react-firebase-auth and change your working directory.

Next, start the project:

$ npm start

Lastly, open http://localhost:3000/ to see your web app.

React Default Project

Material UI

To simplify the UI building process we’ll use Material UI — an open-source React component library that implements Google’s Material Design. The component library includes a comprehensive collection of prebuilt components that work out of the box.

Feel free to swap Material UI for a different UI framework like React Bootstrap or Ant Design.

To add Material UI to your project run:

$ npm install @mui/material @emotion/react @emotion/styled

Material UI uses Roboto font by default. Let’s install it with:

$ npm install @fontsource/roboto

Next, navigate to index.js and add the following imports:

// src/index.js

import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';

React Router

Our web app is going to have the following endpoints:

  1. /login — displays the login form that allows the user to log in.
  2. /register — displays the sign-up form that allows the user to register.
  3. /user — displays user information (email, accessToken, et cetera).

Since we’re building a Single Page Application (SPA) we need a way to implement client-side routing. The easiest way to do it with React is to use react-router-dom.

$ npm install react-router-dom

Next, create a directory named routes within src for all your view components. Then add the following three files to it:

// src/routes/login.jsx

export default function Login() {
  return (
    <h1>Login</h1>
  )
}
// src/routes/register.jsx

export default function Register() {
  return (
    <h1>Register</h1>
  )
}
// src/routes/user.jsx

export default function User() {
  return (
    <h1>User</h1>
  )
}

Create a BrowserRouter and register it in index.js like so:

// src/index.js

const router = createBrowserRouter([
  {
    path: "/",
    element: <Navigate to="login"/>,
  },
  {
    path: "/login",
    element: <Login/>,
  },
  {
    path: "/register",
    element: <Register/>,
  },
  {
    path: "/user",
    element: <User/>,
  },
]);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <RouterProvider router={router}/>
  </React.StrictMode>
);

Don’t forget to add all the required imports at the top of the file:

import {createBrowserRouter, Navigate, RouterProvider} from "react-router-dom";
import Login from "./routes/login";
import Register from "./routes/register";
import User from "./routes/user";

To take care of the “Not Found (404)” error create a new component within your src directory named error-page.jsx with the following contents:

// src/error-page.jsx

import {useRouteError} from "react-router-dom";
import {Container, Typography} from "@mui/material";

export default function ErrorPage() {

  const error = useRouteError();
  console.error(error);

  return (
    <Container maxWidth="xs" sx={{mt: 2}}>
      <Typography variant="h5" component="h1" gutterBottom>
        Oops!
      </Typography>
      <Typography variant="p" component="p" gutterBottom>
        Sorry, an unexpected error has occurred.
      </Typography>
      <Typography variant="p" component="p" gutterBottom>
        <i>{error.statusText || error.message}</i>
      </Typography>
    </Container>
  );
}

Then navigate to index.jsx and add errorElement to the index route like so:

// src/index.js

const router = createBrowserRouter([
  {
    path: "/",
    element: <Navigate to="login"/>,
    errorElement: <ErrorPage/>,  // new
  },
  // ...
]);

Again, make sure to add the required import:

import ErrorPage from "./error-page";

At this point your directory structure should look like this:

react-firebase-auth
├── README.md
├── package.json
├── package-lock.json
├── public
│   └── <public files>
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── error-page.jsx
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── reportWebVitals.js
    ├── routes
    │   ├── login.jsx
    │   ├── register.jsx
    │   └── user.jsx
    └── setupTests.js

Let’s start the app and test if everything works. Run:

$ npm start

Open your favorite web browser and navigate to http://localhost:3000/. That should redirect you to the login screen. After that navigate to http://localhost:3000/register and you should see the register screen.

Form

One of the last things we have to do is to implement the login and register form.

To do that navigate to your routes directory and change login.jsx like so:

// src/routes/login.jsx

import {Alert, Box, Button, Container, Link, TextField, Typography} from "@mui/material";
import {useNavigate} from "react-router-dom";
import {useState} from "react";

export default function Login() {

  const navigate = useNavigate();

  const [error, setError] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const onSubmit = async (event) => {
    event.preventDefault();

    // validate the inputs
    if (!email || !password) {
      setError("Please enter your username and password.");
      return;
    }

    // clear the errors
    setError("");

    // TODO: send the login request
    console.log("Logging in...");
  }

  return (
    <Container maxWidth="xs" sx={{mt: 2}}>
      <Typography variant="h5" component="h1" gutterBottom textAlign="center">
        Login
      </Typography>
      {error && <Alert severity="error" sx={{my: 2}}>{error}</Alert>}
      <Box component="form" onSubmit={onSubmit}>
        <TextField
          label="Email"
          variant="outlined"
          autoComplete="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          sx={{mt: 1}}
          fullWidth
        />
        <TextField
          label="Password"
          variant="outlined"
          type="password"
          autoComplete="new-password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          sx={{mt: 3}}
          fullWidth
        />
        <Button variant="contained" type="submit" sx={{mt: 3}} fullWidth>Login</Button>
        <Box sx={{mt: 2}}>
          Don't have an account yet? <Link href="/register">Register</Link>
        </Box>
      </Box>
    </Container>
  )
}
  1. We used Material UI’s components to build the layout including the form.
  2. State (email and password) is handled via React useState() hook.
  3. Form submission calls onSubmit() which validates the data and displays potential errors.

Next, grab the source code from GitHub and replace the contents of the other two routes:

Run the server again and navigate to your web app. Your views should look like this:

Login/Register/User Authentication Views

Clean up

Remove the following files that were created by Create React App and are no longer required:

  • src/App.css
  • src/App.js
  • src/App.test.js
  • src/logo.svg

Make sure to remove any potential imports from files like index.js et cetera.

Great, our dummy authentication web app is now finished. In the next sections, we’ll upgrade it by adding Firebase Authentication and Back4app Authentication.

Grab the source code for this part of the article from back4app-react-firebase-auth repo.

React Firebase Authentication

In this section of the tutorial, we’ll look at how to integrate Firebase authentication with a React project. As a starting point, we’ll be using the React app we created in the “Project Setup” section.

Check your understanding by working with your own React project as you follow along the tutorial.

Create Project and App

The following steps will require you to have a Firebase account. If you don’t have one yet go ahead and sign up with your Google account.

To work with Firebase we first need to create a project. To create a project login to your Firebase Console and click on “Create a project”:

Firebase Console

Give it a custom name, I’ll name mine react-firebase-auth.

Accept all the terms and conditions and press “Continue”.

Firebase is going to take a few moments to prepare everything required for your project. Once it’s done you’re going to get redirected to the Firebase Project Dashboard.

Next, create a new Firebase Application. Select “Web” since we are using React as our frontend:

Firebase Create Web App

Give it a custom name — or reuse your project name. You don’t have to enable Firebase Hosting since we won’t use it.

Next, click “Register app”:

Firebase Web App Settings

Then select “npm” and take note of your Firebase SDK configuration. Lastly, click on the “Continue to the console” button:

Create Firebase App SDK

Enable Authentication

Before diving into the code we have to enable authentication.

On the left side of the Firebase Console select “Build” and then “Authentication”. Once you get redirected click on “Get started”:

Firebase Authentication Setup

Firebase provides multiple sign in methods. In this tutorial we’ll demonstrate how to use email and password authentication so that’s the only one you have to enable:

Firebase enable Authentication (2)

Firebase SDK

To use Firebase in your React project you first have to install their Software Developer Kit (SDK).

Install it via npm:

$ npm install firebase

Next, create a new file within the src folder named firebase.js with the following contents:

// src/firebase.js

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

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "AIzaSyC4m7VHfM8hy_VUUAlpFCSK3AfrRX4bkQ0",
  authDomain: "react-firebase-auth-d4e6b.firebaseapp.com",
  projectId: "react-firebase-auth-d4e6b",
  storageBucket: "react-firebase-auth-d4e6b.appspot.com",
  messagingSenderId: "1084832623816",
  appId: "1:1084832623816:web:a526bb5b9beff5e26e89fd",
  measurementId: "G-1DXS0RGXPT"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

Make sure to replace the firebaseConfig with your config from the previous step.

Firebase SDK offers different auth methods like:

  1. createUserWithEmailAndPassword(email: string, password: string)
  2. signInWithEmailAndPassword(email: string, password: string)

To utilize these methods let’s create two wrapper functions at the bottom of firebase.js file:

// firebase.js

// ...

export const createUser = async (email, password) => {
  return createUserWithEmailAndPassword(getAuth(app), email, password);
}

export const signInUser = async (email, password) => {
  return signInWithEmailAndPassword(getAuth(app), email, password);
}

Don’t forget to add the import:

import {createUserWithEmailAndPassword, signInWithEmailAndPassword, getAuth} from "firebase/auth";

The functions are self-explanatory:

  1. createUser creates a Firebase user (and returns a response containing user information)
  2. signInUser signs in an existing Firebase user (and returns a response containing user information)

Session Persistence

Firebase doesn’t take care of session persistence on clients’ devices like Parse does.

Because of that, we have to utilize Window.sessionStorage. As the user logs in we have to store their accessToken and retrieve it every time we want to make an authenticated request.

To make things a little bit easier we’ll create a helper class.

Within the src directory create a new file named session.js with the following contents:

// src/storage/session.js

export const startSession = (user) => {
  sessionStorage.setItem("email", user.email);
  sessionStorage.setItem("accessToken", user.accessToken);
}

export const getSession = () => {
  return {
    email: sessionStorage.getItem("email"),
    accessToken: sessionStorage.getItem("accessToken"),
  }
}

export const endSession = () => {
  sessionStorage.clear();
}

export const isLoggedIn = () => {
  return getSession().accessToken;
}

Views

The last thing we have to do is to modify our views to utilize the functions we’ve created and set up the session via helper functions in session.js.

Open login.jsx and change the onSubmit() TODO like so:

// src/routes/login.jsx

import {signInUser} from "../firebase";
import {startSession} from "../session";

const onSubmit = async (event) => {

  // ...

  try {
    let loginResponse = await signInUser(email, password);
    startSession(loginResponse.user);
    navigate("/user");
  } catch (error) {
    console.error(error.message);
    setError(error.message);
  }
}

This code either logs the user in and navigates to /user or displays an error.

Next, change register.jsx onSubmit() TODO like so:

// src/routes/register.jsx

import {createUser} from "../firebase";
import {startSession} from "../session";

const onSubmit = async (event) => {

  // ...

  try {
    let registerResponse = await createUser(email, password);
    startSession(registerResponse.user);
    navigate("/user");
  } catch (error) {
    console.error(error.message);
    setError(error.message);
  }
}

This code either creates a Firebase user or displays an error. Possible errors include:

  1. Username is already taken.
  2. Password is not strong enough.

Lastly, modify user.jsx to use the useEffect() hook to fetch user data and make onLogout() destroy the session:

// src/routes/user.jsx

import {endSession, getSession, isLoggedIn} from "../session";

useEffect(() => {
  if (!isLoggedIn()) {
    navigate("/login");
  }

  let session = getSession();
  setEmail(session.email);

  console.log("Your access token is: " + session.accessToken);
}, [navigate]);

const onLogout = () => {
  endSession();
  navigate("/login");
}

Test

Let’s test the web app to see if everything works.

Start the development server with npm start, open your favorite web browser, and navigate to http://localhost:3000/register. Enter your email, pick a password, and click “Register”:

Firebase Authentication Test

As you register you’ll get redirected to /user where you can see your account information.

To see your accessToken open your browser’s developer console:

Your access token is: 819423a698f9ea9ba3577f20993cb0da98a79ea22ce5d6550b65b69fb36fd438

Lastly, navigate to your Firebase project dashboard to check if a new user has been created:

Firebase Authentication Users

Grab the final source code for this approach from the back4app-react-firebase-auth repo.

React Back4app Authentication

In this section of the tutorial, we’ll look at how to integrate Back4app authentication with a React project. As a starting point, we’ll take the React app we created in the “Project Setup” section.

Check your understanding by working with your own React project as you follow along the tutorial.

Create App

The following steps will require you to have a Back4app account. If you already have it log in otherwise go ahead and sign up for the free account.

To work with Back4app we first need to create an app. As you log in to your dashboard you’ll see the list of your apps. Click on “Build a new app” to create a new app.

Back4app Create App

Give it a custom name, pick your database and then click “Continue”.

Back4app is going to take a few moments to prepare everything required for your app like the database, application layer, scaling, backups & security.

Once your application is ready you will be redirected to your app’s dashboard.

Back4app App Dashboard

App Keys

To connect your React project with Back4app you’ll need to obtain your App Keys. To get your App Keys select “App Settings” on the sidebar and then “Security & Keys”.

Take note of the “Application ID” and the “JavaScript key”.

Back4app Security & Keys

Parse SDK

As you might already know Back4app is based on Parse. Parse is an open-source framework for building application backends. It helps developers to accelerate app development and reduces the total amount of effort required to build an app.

To learn more about Parse take a look at What is Parse?

To set up Back4app authentication we’ll first need to install the Parse SDK. Install it via npm:

$ npm install parse

Next, create a new file named parse.js in the src folder with the following contents:

// src/parse.js

import Parse from "parse/dist/parse";

// Initialize Parse
const PARSE_APPLICATION_ID = '<your_parse_application_id>';
const PARSE_HOST_URL = 'https://parseapi.back4app.com/';
const PARSE_JAVASCRIPT_KEY = '<your_parse_javascript_key>';
Parse.initialize(PARSE_APPLICATION_ID, PARSE_JAVASCRIPT_KEY);
Parse.serverURL = PARSE_HOST_URL;

Make sure to replace PARSE_APPLICATION_ID and PARSE_JAVASCRIPT_KEY with the keys from the previous step.

Parse provides a specialized class called Parse.User that automatically handles much of the functionality required for user account management. Parse.User is a subclass of ParseObject that provides additional helper methods such as signUp(), current(), getUsername() and so on.

Additionally, Parse SDK takes care of your local storage session handling. For example, Parse.User.logIn() stores the user information in the localStorage and Parse.User.signOut() clears the local storage.

Let’s add a few wrapper functions to further simplify working with the Parse authentication system.

Add the following to the bottom of parse.js:

// src/parse.js

// ...

export const doUserRegistration = async (username, password) => {
  return Parse.User.signUp(username, password);
};

export const doUserLogIn = async (username, password) => {
  return Parse.User.logIn(username, password);
};

export const isLoggedIn = async () => {
  return Parse.User.current() != null;
}

export const getUser = async () => {
  return Parse.User.current();
}

export const logOut = async () => {
  return Parse.User.logOut();
}
  1. doUserRegistration() creates a Parse user (using an email and a password)
  2. doUserLogIn() tries to log the user in with the provided credentials
  3. isLoggedIn() checks if localStorage contains session information
  4. getUser() returns the logged-in user from localStorage
  5. logOut() clears localStorage therefore logging out the user

All the functions are asynchronous and return promises.

Views

Let’s utilize the functions we’ve created in the previous step.

Open login.jsx and change the onSubmit() TODO like so:

// src/routes/login.jsx

import {doUserLogIn} from "../parse";

const onSubmit = async (event) => {

  // ...

  try {
    let user = await doUserLogIn(email, password);
    navigate("/user");
  } catch (error) {
    console.error(error.message);
    setError(error.message);
  }
}

This code either logs the user in and navigates to /user or displays an error.

Next, change register.jsx onSubmit() TODO like so:

// src/routes/register.jsx

import {doUserRegistration} from "../parse";

const onSubmit = async (event) => {

  // ...

  let username = email.split("@")[0];
  try {
    let user = await doUserRegistration(username, password);
    user.setEmail(email).save();
    navigate("/user");
  } catch (error) {
    console.error(error.message);
    setError(error.message);
  }
}

Keep in mind that since we are using an email/password registration form we have to extract the user’s username from the email. The username is the part before @.

A better solution would be to create a username/password registration form.

This code either creates a Parse user or displays an error. Possible errors include:

  1. Username is already taken.
  2. Password is not strong enough.

Lastly, modify user.jsx to use the useEffect() hook to fetch user data and make onLogout() call the logout function:

// src/routes/user.jsx

import {getUser, isLoggedIn, logOut} from "../parse";

useEffect(() => {
  (async () => {
    let loggedIn = await isLoggedIn();
    if (!loggedIn) {
      navigate("/login");
    }

    let user = await getUser();
    setEmail(user.getEmail());

    console.log("Your session token is: " + user.getSessionToken());
  })();
}, [navigate]);

const onLogout = async () => {
  await logOut();
  navigate("/login");
}

Great, that’s it!

Test

Let’s make sure our authentication system works as expected.

Start the development server with npm start, open your favorite web browser, and navigate to http://localhost:3000/register. Enter your email, pick a password, and click “Register”:

Back4app Authentication Test

As you register you’ll get redirected to /user where you can see your account information.

To see your sessionToken open your browser’s developer console:

Your session token is: r:90343c307e7bb088e60c348acd8090d1

Lastly, navigate to your Back4app App Dashboard and make sure a user has been created:

Back4app Database Users

Grab the final source code for this approach from the back4app-react-firebase-auth repo.

Conclusion

Authentication and authorization are the most important security measures of modern applications. Authentication is the process of verifying who a user is, while authorization is the process of verifying what a user has access to.

Firebase as well as Back4app both offer great user authentication systems. They each come with their own pros and cons that should be considered when starting a project.

Further reading

FAQ

What is authentication?

Authentication is the process of verifying the identity of a user or device. It is a security measure that is designed to ensure that only authorized users or devices are able to access a system, network, or resource.

What are the authentication types?

– Username and password
– Single sign-on (SSO)
– Social authentication
– Token-based authentication
– Biometric authentication
– Certificate-based authentication

How to set up authentication with Firebase?

1. Log in to Firebase with your Google Account
2. Create a Firebase project and a Firebase app
3. Enable Firebase Authentication
4. Install Firebase SDK and initialize it
5. Use Firebase SDK authentication functions in your views
6. Use Window.sessionStorage to handle sessions

How to set up authentication with Back4app?

1. Create an account on Back4app
2. Create a Back4app app
3. Install Parse SDK and initialize it
4. Use ParseJS authentication functions in your views


Leave a reply

Your email address will not be published.