106

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>
cbr
  • 12,563
  • 3
  • 38
  • 63
Tanner Semerad
  • 12,472
  • 12
  • 40
  • 49
  • 2
    If they aren't logged in, redirect to the login handler. Also note that the client has access to all of the JS it loads, so don't store sensitive info in it. – Colonel Thirty Two Jun 27 '15 at 03:17
  • @Tanner Semerad do you have any github repository about how you achieved this briefly. – jit Jan 28 '16 at 05:11
  • @jit I don't, sorry. The answer from miciek below was what I needed, but keep in mind that this was prior to react-router 1.0. I know a number of things have changed since 1.0 was released, but it's mostly similar. – Tanner Semerad Jan 28 '16 at 16:06
  • @jayair's answer is what I'm using now, and it works great – Tanner Semerad Aug 31 '17 at 23:12

8 Answers8

120

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

jayair
  • 1,241
  • 1
  • 9
  • 10
  • If my original code was using , how would I make it work with this example? – Bran Jul 12 '17 at 18:45
  • 1
    I have very similar code, but my question is, is is secured enough? I mean may be an attacker can change the JS minified code such that replacing `this.props.isLoggedIn` with `true` and bypass login? – karim elhelawy Oct 25 '17 at 07:29
  • 5
    @karimelhelawy That is true and because of that you need to enforce authentication in your server's API. – cbr Oct 27 '17 at 15:07
  • 7
    `` is deprecated in v1.0, you should use ``. – DrDirk Feb 10 '18 at 14:00
  • 1
    `componentWillMount` are soon going to be deprecated. [Read it in the blog post on reactjs.org](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html). Instead I would go with the answer @jacob provided. – Tom Mar 29 '18 at 14:59
  • If you do it on `ComponentWillMount`, you are causing a side effect. I think the best place to call `checkAuth()` is on `ComponentDidMount`. Still, I think this answer is the best solution presented here. [React docs](https://reactjs.org/docs/react-component.html#unsafe_componentwillmount) – leosbrf Aug 31 '18 at 02:31
  • here is updated code in html write in auth component write this.props.history.push(`/login?redirect=${redirect}`); – nirmal Aug 14 '19 at 08:44
43

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/>
  )
)}/>
Jakob Jingleheimer
  • 30,952
  • 27
  • 76
  • 126
5

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} />
];
gwendall
  • 920
  • 15
  • 22
  • This is very simple, which is good. The thing is that you usually want to recognise the same routes either if you are logged out or in, so you can properly redirect to login if the user was logged out. You usually want routes to be the same, but to behave in a different way depending on the logged in status. Also with your solution you are adding duplication, by creating the same Route in 2 different locations whicih is harder to maintain. – Rafael Porras Lucena Apr 01 '20 at 13:21
3

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()).

Michał Płachta
  • 687
  • 6
  • 12
  • 2
    Mixins are bad idea to move on. [Read more](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.egtnavn6d) – boldnik Mar 15 '16 at 10:17
  • A much better way which I found : https://github.com/reactjs/react-router/tree/master/examples/auth-flow – boldnik Mar 15 '16 at 10:28
  • 1
    Mixins have been removed form ES6 and React has been deprecating them. – Pier Aug 19 '16 at 22:14
2

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" />))}/>
Ankit Kumar Rajpoot
  • 5,188
  • 2
  • 38
  • 32
1

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/.

Ambroise Rabier
  • 3,636
  • 3
  • 27
  • 38
0

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.

yuchen huang
  • 257
  • 1
  • 2
  • 10
-2

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 :)

Jim
  • 1,123
  • 13
  • 29
  • 2
    Right, so how would I stop the "Overview" class to be imported if Auth hasn't gone through, or what does the "Main" handler look like? For instance, what if "Overview" has a dependency that requires an authenticated app to run? Because it is imported to run on the router, all its dependencies will have been imported too, and thus you have a broken app right? – Marais Rossouw Jul 08 '15 at 11:14
  • This does not answer the question that was asked – HermannHH Jul 03 '17 at 18:57