Protected routes with React Router v5

React Router makes it extremely easy to define routing in your React
application, but out of the box doesn’t concern itself with which routes should
require authentication and which ones should now.

I’ve solved this problem in a past life, using React Router v4 and looking back
at the code we had written, it left quite a bit to be desired. So much so that
when I tried to copy pasta the code into a new project of mine, it didn’t work
as expected.

Fast forward to after researching and reading a handful of other posts out
there, some of which actually seemed to be plagiarized from each other, I
realized that most of what I was reading was also in reference to React Router
v4.

Between you and me, I don’t really know what the main differences are between v4
and v5 of React router. What I do know, is that I wasn’t totally satisfied with
anything I had read so I had set out to solve the problem my way.

The big things I had found to be missing were in regard to handling routes that
should only be available when a user is unauthenticated and nobody seems to have
documented the process while using TypeScript or a proper style guide.

I’m a huge fan of TypeScript and I don’t write much code without the safety net
of the Airbnb style guide, so most of the code I was slinging around needed
massaged quite a bit to make it work for me.

That said, to handle juggling both routes that require authentication, and those
that require the user to be unauthenticated, I added two new components that act
as wrappers for React Router’s Route component.

The following code will assume that you have some sort of helper method to tell
you if the user is properly authenticated. For me, that’s just an
isAuthenticated method that checks to see if we have a JSON web token
available.

Also, note the limited nature of which route properties I’m passing back to the
Route component. I’m not sure if that will be enough long term, but for the
sake of not triggering eslint, specifically the react/jsx-props-no-spreading
rule, I’m limiting the scope of what I’m passing through.

AuthenticatedRoute.component.tsx

import React from 'react'
import { Redirect, Route, RouteProps } from 'react-router-dom'

import { isAuthenticated } from '../helpers'

const RouteAuthenticated = ({ component: Component, path }: RouteProps) => {
  if (!isAuthenticated()) {
    return <Redirect to="/login" />
  }

  return <Route component={Component} path={path} />

export default RouteAuthenticated

UnauthenticatedRoute.component.tsx

import React from 'react'
import { Redirect, Route, RouteProps } from 'react-router-dom'

import { isAuthenticated } from '../helpers'

const RouteUnauthenticated = ({ component: Component, path }: RouteProps) => {
  if (isAuthenticated()) {
    return <Redirect to="/dashboard" />
  }

  return <Route component={Component} path={path} />

export default RouteUnauthenticated

App.component.tsx

import React from 'react'
import { Router, Redirect, Switch } from 'react-router-dom'

import { history, isAuthenticated } from '../helpers'

import Dashboard from './Dashboard.component'
import LoginForm from './LoginForm.component'
import RouteAuthenticated from './RouteAuthenticated.component'
import RouteUnauthenticated from './RouteUnauthenticated.component'
import SignupForm from './SignupForm.component'

const fallbackUri = `${isAuthenticated() ? '/dashboard' : '/login'}`

const App = () => (
  <div className="container">
    <Router history={history}>
      <Switch>
        <RouteUnauthenticated path="/signup" component={SignupForm} />
        <RouteUnauthenticated path="/login" component={LoginForm} />

        <RouteAuthenticated path="/dashboard" component={Dashboard} />

        <Redirect to={fallbackUri} />
      </Switch>
    </Router>
  </div>


export default App

With the addition of the two new components wrapping the Route component, I’m
able to route accordingly, so folks that are already authenticated can’t get to
the login or sign up forms.

You certainly could combine the RouteAuthenticated and RouteUnauthenticated
components into a single component that accepts a new property to tell you if
it should require authentication or not. If you’re using TypeScript this will
probably result in needing to extend the ReactProps since your new property
won’t exist on that type / interface.

What about routes that should be available to both authenticated users and users
that have yet to authenticate? Just utilize the Route component from React
Router and you should be good to go!

Josh Sherman - The Man, The Myth, The Avatar

About Josh

Husband. Father. Pug dad. Musician. Founder of Holiday API, Head of Engineering and Emoji Specialist at Mailshake, and author of the best damn Lorem Ipsum Library for PHP.


If you found this article helpful, please consider buying me a coffee.