Use Authentication To Protect React Routes
By: Aaron Schuyler
Tags: React Router, Front End Development, Authentication, JavaScript, React

I took this picture myself!

You read the title, and you know why you’re here! Want to make protected react routes in 2020? Let’s go! (You need to be familiar with the router already to follow this guide)

First have a react project. I like to separate my router from my app.js file into its own router.js file. My app.js file looks like this:

//App.js

import './App.css'

import React from 'react'

import Router from './components/application/router.js' export default function App() {

return (

<div className='App'>

<Router />

</div>

)

}

That’s right, I don’t use semicolons; except in my English.

Let’s set up the router.js file:

//router.js

//pretend we imported all of our components

import React from 'react'

import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom' export default function Router() {

return (

<BrowserRouter>

<Navigation />

// I like to put my nav links into a seperate file

<Switch>

<Route exact path='/'>

<PublicComponent />

</Route>

</Switch>

</BrowserRouter>

)

}

Here we have a simple router with only one route, the root route which is public. In order to add authentication to our soon to be created private routes, we need to create a context for our router. If you follow this tutorial, you don’t need a strong understanding of a context, but here is the official description:

“Context provides a way to pass data through the component tree without having to pass props down manually at every level.”

Please read Context — React (reactjs.org) for more details.

Ok, so we are going to start by creating a context and a hook which you can read about here: Introducing Hooks — React (reactjs.org).

//hooks/auth.js

import React, { useState, useEffect, useContext, createContext } from 'react' const authContext = createContext() export function ProvideAuth({ children }) {

const auth = useProvideAuth()

return <authContext.Provider value={auth}> { children } </authContext.Provider>

} export const useAuth = () => useContext(authContext) function useProvideAuth() {

return []

}

This is our context and hook. Let’s talk about what we have so far, (hint: it isn’t much).

After importing the necessary requirements, we create a context. We then export our context as a component. Don’t worry too much about how this all works, it is out of our scope to get into that. Really the most important thing to note is that whatever is returned by useProvideAuth() is now accessible by using the useAuth() hook.

With this in mind, let’s create a useful return value!

//hooks/auth.js //we need a base URL for our sign in function

const baseUrl = process.env.REACT_APP_BASE_URL *** function useProvideAuth() {

const [user, setUser] = useState(null) const signin = (credentials) => {

fetch(baseUrl + 'login', {

method: 'POST',

credentials: 'include',

headers: {

'Content-Type': 'application/json',

'Accepts': 'application/json'

},

body: JSON.stringify(credentials)

})

.then(res => res.json())

.then(json => setUser(json.user))

} useEffect(() => {

//check authentication

}, []) return {

user,

signin

}

}

We used React’s useState() functionality to set a constant called user and create a function that let’s us update that. As you can see in the second .then() after our fetch() we have setUser(json.user) .

If you want to include functionality to check if a user is logged in when they visit the site it should be done in the useEffect() callback.

Back to the Router, let’s use our newly created context:

//router.js *** export default function Router() {

return (

<ProvideAuth>

<BrowserRouter>

<Navigation />

// I like to put my nav links into a seperate file

<Switch>

<Route exact path='/'>

<PublicComponent />

</Route>

</Switch>

</BrowserRouter>

</ProvideAuth>

)

}

What next? How do we use the value of our auth variable in the context? Let’s create a private route. If you didn’t know you could create fancy custom routes whenever you wanted- well, your mind is about to be blown!

//router.js *** function PrivateRoute({ children, ...rest }) {

const auth = useAuth()

return (

<Route {...rest} render={({ location }) =>

auth.user ? (children) :

(<Redirect to={{ pathname: '/login', state: { from: location } }} />)

}

/>

)

}

Routes are basically just components! The first argument is always the route’s children. ...rest is just the rest of the params in the route’s tag (if any). As you can see, we initialize our useAuth() hook.

In our return value, we just take a standard React route component and give it the ...rest in the render param, we pass in a lovely function that takes the location as an argument. This function returns a ternary ‘if’ statement that checks the value of auth.user and returns either the component nested between <PrivateRoute></PrivateRoute> or redirects us to the login route.

Back in our router we can implement the PrivateRoute :

//router.js *** export default function Router() {

return (

<ProvideAuth>

<BrowserRouter>

<Navigation />

// I like to put my nav links into a seperate file

<Switch>

<Route exact path='/'>

<PublicComponent />

</Route>

<PrivateRoute path='private'>

<PrivateComponent />

</PrivateRoute>

</Switch>

</BrowserRouter>

</ProvideAuth>

)

}

Yes it is that simple! But we aren’t done just yet.

In our login component, (you’re on your own with this one) we need to include a reference to our login function.

//hypotheticalLoginComponent.js const auth = useAuth() *** const onSubmit = () => {

auth.signin(credentials)

}

In your login component, you should use useAuth() to submit the login credentials to our wonderful auth hook.

If you like to abstract all your API calls to another file, you can also just have your signin() function call setUser() and call that signin function after the API call resolves.

Here are the final files for router.js and auth.js