DEV Community

Cover image for Firebase Authentication with React and Guarded Routes
Justin Brooks
Justin Brooks

Posted on

Firebase Authentication with React and Guarded Routes

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 and source code on github.


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.

npx create-react-app firebase-auth-react
Enter fullscreen mode Exit fullscreen mode

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

yarn add react-router-dom firebase@beta
Enter fullscreen mode Exit fullscreen mode

Next I'll create a firebase helper file called firebase.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 }
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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.

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

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.

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>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

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.

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>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

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.

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>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

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 if you'd like to learn more. You should also check out my website codingwithjustin.com where I post more content similar to this one.

Top comments (2)

Collapse
 
bansalrahul14 profile image
Rahul bansal • Edited

There is a problem with this method. It makes it impossible to reach the url.
so the flow is I visited /a endpoint. It will be redirected to the /login and post that it will be redirected to / path.

I added a success url in /login to be the /a and it is ending up in an infinite redirect now.

Collapse
 
ashishk1331 profile image
Ashish Khare😎

Guys, there is new competitor in the town Supabase. If someone wants to ditch the google policies and maintain our app on open source code, then definitely supabase is your go to. I'm not a promoter just don't like firebase, then heard about supabse. One could easily maintain auth and database on supabase while host and serve functions on netlify. Great combo!

Plus, thanks to the author of this post for writing this infomercial. Nice post!