How to restrict access to routes in react-router?

JavascriptReactjsReact Router

Javascript Problem Overview


Does anyone know how to restrict access to particular routes in react-router? I want to check if the user is logged in before allowing access to a particular route. I thought it would be simple, but the docs aren't clear how to do it.

Is this something I should set up where I define my <Route> components, or should I be handling it inside my component handlers?

<Route handler={App} path="/">
  <NotFoundRoute handler={NotFound} name="not-found"/>
  <DefaultRoute handler={Login} name="login"/>
  <Route handler={Todos} name="todos"/> {/* I want this to be restricted */}
</Route>

Javascript Solutions


Solution 1 - Javascript

Update (Aug 16, 2019)

In react-router v4 and using React Hooks this looks a little different. Let's start with your App.js.

export default function App() {
  const [isAuthenticated, userHasAuthenticated] = useState(false);

  useEffect(() => {
    onLoad();
  }, []);

  async function onLoad() {
    try {
      await Auth.currentSession();
      userHasAuthenticated(true);
    } catch (e) {
      alert(e);
    }
  }

  return (
    <div className="App container">
      <h1>Welcome to my app</h1>
      <Switch>
        <UnauthenticatedRoute
          path="/login"
          component={Login}
          appProps={{ isAuthenticated }}
        />
        <AuthenticatedRoute
          path="/todos"
          component={Todos}
          appProps={{ isAuthenticated }}
        />
        <Route component={NotFound} />
      </Switch>
    </div>
  );
}

We are using an Auth library to check if the user is currently authenticated. Replace this with your auth check function. If so then we set the isAuthenticated flag to true. We do this when our App first loads. Also worth mentioning, you might want to add a loading sign on your app while the auth check is being run, so you don't flash the login page every time you refresh the page.

Then we pass the flag to our routes. We create two type of routes AuthenticatedRoute and UnauthenticatedRoute.

The AuthenticatedRoute.js looks like this.

export default function AuthenticatedRoute({ component: C, appProps, ...rest }) {
  return (
    <Route
      {...rest}
      render={props =>
        appProps.isAuthenticated
          ? <C {...props} {...appProps} />
          : <Redirect
              to={`/login?redirect=${props.location.pathname}${props.location.search}`}
            />}
    />
  );
}

It checks if isAuthenticated is set to true. If it is, then it'll render the desired component. If not, then it'll redirect to the login page.

The UnauthenticatedRoute.js on the other hand looks like this.

export default ({ component: C, appProps, ...rest }) =>
  <Route
    {...rest}
    render={props =>
      !appProps.isAuthenticated
        ? <C {...props} {...appProps} />
        : <Redirect to="/" />}
  />;

In this case, if the isAuthenticated is set to false, it'll render the desired component. And if it is set to true, it'll send you to the homepage.

You can find detailed versions of this on our guide - https://serverless-stack.com/chapters/create-a-route-that-redirects.html.

Older version

The accepted answer is correct but Mixins are considered to be harmful (https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html) by the React team.

If somebody comes across this question and is looking for the recommended way to do this, I'd suggest using Higher Order Components instead of Mixins.

Here is an example of a HOC that'll check if the user is logged in before proceeding. And if the user is not logged in, then it'll redirect you to the login page. This component takes a prop called isLoggedIn, that is basically a flag that your application can store to denote if the user is logged in.

import React from 'react';
import { withRouter } from 'react-router';

export default function requireAuth(Component) {

  class AuthenticatedComponent extends React.Component {

    componentWillMount() {
      this.checkAuth();
    }
    
    checkAuth() {
      if ( ! this.props.isLoggedIn) {
        const location = this.props.location;
        const redirect = location.pathname + location.search;

        this.props.router.push(`/login?redirect=${redirect}`);
      }
    }

    render() {
      return this.props.isLoggedIn
        ? <Component { ...this.props } />
        : null;
    }

  }

  return withRouter(AuthenticatedComponent);
}

And to use this HOC, just wrap it around your routes. In case of your example, it would be:

<Route handler={requireAuth(Todos)} name="todos"/>

I cover this and a few other topics in a detailed step-by-step tutorial here - https://serverless-stack.com/chapters/create-a-hoc-that-checks-auth.html

Solution 2 - Javascript

There is (now?) an example of this in React Router 4's docs for Redirect

import { Route, Redirect } from 'react-router'

<Route exact path="/" render={() => (
  loggedIn ? (
    <Redirect to="/dashboard"/>
  ) : (
    <PublicHomePage/>
  )
)}/>

Solution 3 - Javascript

react-router encourages a declarative approach for your router, you should make your router as dumb as possible and avoid putting your routing logic in your components.

Here is how you can do it (assuming you pass it the loggedIn prop):

const DumbRouter = ({ loggedIn }) => (
  <Router history={history}>
    <Switch>
      {[
        !loggedIn && LoggedOutRoutes,
        loggedIn && LoggedInRouter,
        <Route component={404Route} />
      ]}
    </Switch>
  </Router>
);

const LoggedInRoutes = [
  <Route path="/" component={Profile} />
];

const LoggedOutRoutes = [
  <Route path="/" component={Login} />
];

Solution 4 - Javascript

If you want to use authentication across your whole application, you need to store some data application-wide (e.g. token). You can set up two React mixins that are responsible for managing $auth object. This object shouldn't be available outside those two mixins. Here's example of that:

define('userManagement', function() {
    'use strict';

    var $auth = {
        isLoggedIn: function () {
            // return something, e.g. using server-stored data
        }
    };

    return {
        Authenticator: {
           login: function(username, password) {
               // modify $auth object, or call server, or both
           }
        },

        NeedsAuthenticatedUser: {
            statics: {
                willTransitionTo: function (transition) {
                    if (!$auth.isLoggedIn()) {
                        transition.abort();
                    }
                }
            }
        }
    };
});

Then you can just mixin Authenticator mixing to your login components (login screen, login popup, etc) and call this.login function when you have all the data necessary.

The most important thing is protecting your components by mixing in NeedsAuthenticatedUser mixin. Each component that needs authenticated user will have to look like that:

var um = require('userManagement');

var ProtectedComponent = React.createClass({
    mixins: [um.NeedsAuthenticatedUser]
    // ...
}

Note that NeedsAuthenticatedUser uses react-router API (willTransitionTo and transition.abort()).

Solution 5 - Javascript

You can use HOC and auth is a variable you can change value true or false means(authorization)

<Route path="/login" component={SignIn} />
<Route path="/posts" render = {() => (auth ?  (<Post />) : (<Redirect to="/login" />))}/>

Solution 6 - Javascript

private-route.tsx

import {Redirect, Route, RouteProps} from 'react-router';
import * as React from 'react';

interface PrivateRouteProps extends RouteProps {
  /**
   * '/login' for example.
   */
  redirectTo: string;

  /**
   * If true, won't redirect.
   * We are using a function instead of a bool, a bool does not seem to be updated
   * after having successfully authenticated.
   */
  isLogged: () => boolean;
}


export function PrivateRoute(props: PrivateRouteProps) {
  // `component: Component` is not typing, it assign the value to a new variable.
  let { isLogged, redirectTo, component: Component, ...rest }: any = props;

  // error: JSX type element Component does not have call signature or ... AVOIDED BY ADDING ANY, still work,
  // and did not find a proper way to fix it.
  return <Route {...rest} render={(props) => (
    isLogged()
      ? <Component {...props}/>
      : <Redirect to={{
        pathname: redirectTo,
        state: { from: props.location }
      }} />
  )} />;
}

Usage:

        <PrivateRoute exact={true} 
                      path="/admin/" 
                      redirectTo={'/admin/login'} 
                      isLogged={this.loginService.isLogged} 
                      component={AdminDashboardPage}/>
        <Route path="/admin/login/" component={AdminLoginPage}/>

Based on https://tylermcginnis.com/react-router-protected-routes-authentication/.

Solution 7 - Javascript

You can avoid to render component before confirming authentication, like as below:

import { useState, useEffect, useRef } from 'react';
import { useHistory } from 'react-router-dom';

const Route = () => {
    const [loading, sertLoading] = useState(true);
    const history = useHistory();

    const ref = useRef<Function>({});

    // must use ref!
    ref.current.routeGuard = () => {
        const authenticationHandler = (): boolean => {
         // do authentication here
        }
        sertLoading(true);
        const go = authenticationHandler();
        if (go === false) {
            history.goBack();
        }
        sertLoading(false);
    } 

    useEffect(() => {
        ref.current.routeGuard();
        history.listen(() => {
            ref.current.routeGuard();
        });
    }, []);

    return (
        <>
            {!loading && <YourRouteComponent />}
        </>
    )
};

Or simply, yarn add react-routers, which component have props beforeEach, beforeRoute like Vue Route.

Solution 8 - Javascript

usually a logged in user will be granted a token, and uses this token for any communication with server. What we usually do is define a root page, and things build on top of that page. this root page does localisation, authentication and other configurations for you.

here's an example

Routes = (
    <Route path="/" handler={Root}>
        <Route name="login" handler={Login} />
        <Route name="forget" handler={ForgetPassword} />
        <Route handler={Main} >
            <Route name="overview" handler={Overview} />
            <Route name="profile" handler={Profile} />
            <DefaultRoute handler={Overview} />
        </Route>
        <DefaultRoute handler={Login} />
        <NotFoundRoute handler={NotFound} />
    </Route>
);

on your root page, check for token null or authenticate the token with server to see if user is valid login.

hope this helps :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTanner SemeradView Question on Stackoverflow
Solution 1 - JavascriptjayairView Answer on Stackoverflow
Solution 2 - JavascriptJakob JingleheimerView Answer on Stackoverflow
Solution 3 - JavascriptgwendallView Answer on Stackoverflow
Solution 4 - JavascriptMichał PłachtaView Answer on Stackoverflow
Solution 5 - JavascriptAnkit Kumar RajpootView Answer on Stackoverflow
Solution 6 - JavascriptAmbroise RabierView Answer on Stackoverflow
Solution 7 - Javascriptyuchen huangView Answer on Stackoverflow
Solution 8 - JavascriptJimView Answer on Stackoverflow