Firebase Authentication with React and Guarded Routes
Tags: react, firebase
Published: 2021-05-24 19:01:36

Authentication is one of those things that just always seems to take a lot more effort than we want it to, yet it's always a feature every website needs.

Firebase makes this process super easy. So today lets create a React application where we use firebase authentication and router guards.

It will have a total of 3 pages. One for signing up, another for logging, and a home page that is only accessible if the user is authenticated.


---

You can find the full write up at [codingwithjustin.com](http://codingwithjustin.com) and source code on [github](https://github.com/codingwithjustin/firebase-v9-auth-react).

{% youtube MEAjrlQ35HQ %}

---


# Firebase

We'll need to set up a firebase project to get started.

Head over to Firebase and create a new application. The process should be straightforward and only take a few seconds. We'll also need to enable the auth options before we start building anything. First, make sure you enable email/password in the Authentication tab,  by clicking on Sign-methods.

I'll also be using **version 9** of firebase which is currently in beta. It makes the firebase tree shakeable as well as provides some other improvements.


# Project Setup

We'll need to create a new project using the create react app CLI.

```bash
npx create-react-app firebase-auth-react
```

Once completed we'll also install `react-router-dom` and `firebase@beta` for version 9.

```bash
yarn add react-router-dom firebase@beta
```

Next I'll create a firebase helper file called `firebase.js`. 

```js
import { getAuth, onAuthStateChanged } from '@firebase/auth'
import { initializeApp } from 'firebase/app'
import { useState, useEffect, useContext, createContext } from 'react'

export const firebaseApp = initializeApp({ /* config */ })

export const AuthContext = createContext()

export const AuthContextProvider = props => {
  const [user, setUser] = useState()
  const [error, setError] = useState()

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(getAuth(), setUser, setError)
    return () => unsubscribe()
  }, [])
  return <AuthContext.Provider value={{ user, error }} {...props} />
}

export const useAuthState = () => {
  const auth = useContext(AuthContext)
  return { ...auth, isAuthenticated: auth.user != null }
}
```

Here we'll initialize our configuration using the values we got from creating a project. We'll also create an auth context for holding the state of the current user signed in.

Context in react is a tool that allows you to share state throughout the whole react component without having to pass it down by props. Instead, we can initialize a `Context Provider`, pass in our state as value, and then we can access it anywhere by calling `useContext` with our context object. In our case will want to pass in the user's state which we get from the `onAuthStateChanged` listener. We'll also want to make sure we unsubscribe from this event when the component is unmounted.

# Routing

In our `App.js` we'll need to add our routing option and link these to each of our pages. However, doing this won't protect our routes from unauthenticated users. To protect our routes we'll create a custom component which Ill call `AuthenticatedRoute`.

```js
const AuthenticatedRoute = ({ component: C, ...props }) => {
  const { isAuthenticated } = useAuthState()
  console.log(`AuthenticatedRoute: ${isAuthenticated}`)
  return (
    <Route
      {...props}
      render={routeProps =>
        isAuthenticated ? <C {...routeProps} /> : <Redirect to="/login" />
      }
    />
  )
}
```

We'll call the `useAuthState` hook we created earlier to check if the user is authenticated. If they are authenticated we'll render the page, otherwise, we'll redirect them to the login page.

Let's also create a simple UnauthenticatedRoute that will use for the login page. This component is similar to the logic above expect we will only want to render the component if the user is not authenticated.

```js
const UnauthenticatedRoute = ({ component: C, ...props }) => {
  const { isAuthenticated } = useAuthState()
  console.log(`UnauthenticatedRoute: ${isAuthenticated}`)
  return (
    <Route
      {...props}
      render={routeProps =>
        !isAuthenticated ? <C {...routeProps} /> : <Redirect to="/" />
      }
    />
  )
}
```

It's also worth mentioning, you might want to add a loading sign-on in your app while the auth check is being run. This way you don't flash a page every time you refresh.

# Pages

Now, let's go through each page and those up.

## Login

For the login page, we'll create a form that asks the user for an email address and password. When the user clicks the submit button, we'll grab those two values from the form element and pass them into the `signInWithEmailAndPassword` function. Once it's successful the user will be considered logged in and will automatically be redirected to the home page. 

```js
import { useCallback } from 'react'
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth'

export const Login = () => {
  const handleSubmit = useCallback(async e => {
    e.preventDefault()

    const { email, password } = e.target.elements
    const auth = getAuth()
    try {
      await signInWithEmailAndPassword(auth, email.value, password.value)
    } catch (e) {
      alert(e.message)
    }
  }, [])

  return (
    <>
      <h1>Login</h1>
      <form onSubmit={handleSubmit}>
        <input name="email" placeholder="email" type="email" />
        <input name="password" placeholder="password" type="password" />
        <button type="submit">Login</button>
      </form>
    </>
  )
}
```

I recommend you add better error handling here but I'm going to wrap this in a try-catch statement and alert the user with any error messages.

If we wanted to redirect to a specific URL we could call the `useLocation` hook from the react router and push a path onto it.

## Signup

The signup page is also going to be very similar, we'll create another form that asks for their email and password. On submit we'll grab those values and call the `createUserWithEmailAndPassword` function. If the user signs in is successfully they will automatically get redirect to the home page.

```js
import { useCallback } from 'react'
import { getAuth, createUserWithEmailAndPassword } from 'firebase/auth'

export const SignUp = () => {
  const handleSubmit = useCallback(async e => {
    e.preventDefault()

    const { email, password } = e.target.elements
    const auth = getAuth()
    try {
      await createUserWithEmailAndPassword(auth, email.value, password.value)
    } catch (e) {
      alert(e.message)
    }
  }, [])

  return (
    <>
      <h1>Sign Up</h1>
      <form onSubmit={handleSubmit}>
        <input name="email" placeholder="email" type="email" />
        <input name="password" placeholder="password" type="password" />
        <button type="submit">Sign Up</button>
      </form>
    </>
  )
}
```

## Home Page

For the Home page, We'll put a nice welcome message and show the user's email. We'll also create a button that will call the auth signout function.

```js
import { getAuth, signOut } from 'firebase/auth'
import { useAuthState } from './firebase'

export const Home = () => {
  const { user } = useAuthState()

  return (
    <>
      <h1>Welcome {user?.email}</h1>
      <button onClick={() => signOut(getAuth())}>Sign out</button>
    </>
  )
}
```

# Conclusion

Adding authentication and access control to your application doesn't have to be a hassle. Both the setup step and, more importantly, the maintenance over time, are handled with modern platforms like Firebase.

I have a community over on [discord](https://codingwithjustin.com/discord) if you'd like to learn more. You should also check out my website [codingwithjustin.com](http://codingwithjustin.com) where I post more content similar to this one.