¿Cómo restringir el acceso a las rutas en react-router?

9 minutos de lectura

avatar de usuario
curtidor semerad

¿Alguien sabe cómo restringir el acceso a rutas particulares en react-router? Quiero verificar si el usuario ha iniciado sesión antes de permitir el acceso a una ruta en particular. Pensé que sería simple, pero los documentos no tienen claro cómo hacerlo.

¿Es esto algo que debería configurar donde defino mi <Route> componentes, o debería manejarlo dentro de mis controladores de componentes?

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

  • Si no han iniciado sesión, redirija al controlador de inicio de sesión. También tenga en cuenta que el cliente tiene acceso a todo el JS que carga, así que no almacene información confidencial en él.

    – Coronel Treinta y Dos

    27 de junio de 2015 a las 3:17

  • @Tanner Semerad, ¿tiene algún repositorio de github sobre cómo logró esto brevemente?

    – jit

    28 de enero de 2016 a las 5:11

  • @jit No, lo siento. La respuesta de miciek a continuación era lo que necesitaba, pero tenga en cuenta que esto fue anterior a react-router 1.0. Sé que varias cosas han cambiado desde que se lanzó 1.0, pero en su mayoría es similar.

    – Tanner Semerad

    28 de enero de 2016 a las 16:06

  • La respuesta de @jayair es lo que estoy usando ahora, y funciona muy bien

    – Tanner Semerad

    31 de agosto de 2017 a las 23:12

avatar de usuario
jayair

Actualización (16 de agosto de 2019)

En react-router v4 y usando React Hooks, esto se ve un poco diferente. Empecemos con tu 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>
  );
}

estamos usando un Auth biblioteca para comprobar si el usuario está actualmente autenticado. Reemplace esto con su función de verificación de autenticación. Si es así, establecemos el isAuthenticated bandera a true. Hacemos esto cuando nuestra aplicación se carga por primera vez. También vale la pena mencionar que es posible que desee agregar un signo de carga en su aplicación mientras se ejecuta la verificación de autenticación, para que no muestre la página de inicio de sesión cada vez que actualice la página.

Luego pasamos la bandera a nuestras rutas. Creamos dos tipos de rutas AuthenticatedRoute y UnauthenticatedRoute.

los AuthenticatedRoute.js Se ve como esto.

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}`}
            />}
    />
  );
}

Comprueba si isAuthenticated se establece en true. Si es así, renderizará el componente deseado. De lo contrario, se redirigirá a la página de inicio de sesión.

los UnauthenticatedRoute.js por otro lado se parece a esto.

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

En este caso, si el isAuthenticated se establece en false, renderizará el componente deseado. Y si se establece en verdadero, lo enviará a la página de inicio.

Puede encontrar versiones detalladas de esto en nuestra guía: https://serverless-stack.com/chapters/create-a-route-that-redirects.html.

Versión antigua

La respuesta aceptada es correcta, pero los Mixins se consideran dañinos (https://facebook.github.io/react/blog/2016/07/13/mixins-considered-dañino.html) por el equipo de React.

Si alguien se encuentra con esta pregunta y está buscando la forma recomendada de hacerlo, sugeriría usar componentes de orden superior en lugar de Mixins.

Aquí hay un ejemplo de un HOC que verificará si el usuario ha iniciado sesión antes de continuar. Y si el usuario no ha iniciado sesión, lo redirigirá a la página de inicio de sesión. Este componente toma un accesorio llamado isLoggedInque es básicamente un indicador que su aplicación puede almacenar para indicar si el usuario ha iniciado sesión.

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);
}

Y para usar este HOC, simplemente envuélvalo alrededor de sus rutas. En el caso de tu ejemplo, sería:

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

Cubro este y algunos otros temas en un tutorial detallado paso a paso aquí: https://serverless-stack.com/chapters/create-a-hoc-that-checks-auth.html

  • Si mi código original usaba , ¿cómo haría que funcionara con este ejemplo?

    – Salvado

    12/07/2017 a las 18:45

  • Tengo un código muy similar, pero mi pregunta es, ¿es lo suficientemente seguro? Quiero decir que puede ser que un atacante pueda cambiar el código minificado de JS de tal manera que reemplace this.props.isLoggedIn con true y omitir el inicio de sesión?

    – karim elhelawy

    25 de octubre de 2017 a las 7:29

  • @karimelhelawy Eso es cierto y, por eso, debe aplicar la autenticación en la API de su servidor.

    – cbr

    27 oct 2017 a las 15:07

  • <Route handler={}/> está en desuso en v1.0, debe usar <Route component={} />.

    – DrDirk

    10/02/2018 a las 14:00

  • componentWillMount pronto van a ser obsoletos. Léalo en la publicación del blog en reactjs.org. En cambio, iría con la respuesta proporcionada por @jacob.

    – Tomás

    29 de marzo de 2018 a las 14:59

Hay (¿ahora?) un ejemplo de esto en los documentos de React Router 4 para Redirect

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

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

  • ¿Cómo puedo usar “loggedIn” como función o variable? puedes explicarlo un poco

    – Kunvar Singh

    24 de junio de 2018 a las 11:56

  • @KunvarSingh probablemente debería ser una función porque el valor cambia.

    –Jakob Jingleheimer

    24 de junio de 2018 a las 12:09

react-router fomenta un enfoque declarativo para su enrutador, debe hacer que su enrutador sea lo más tonto posible y evitar poner su lógica de enrutamiento en sus componentes.

Así es como puede hacerlo (suponiendo que lo pase el loggedIn apuntalar):

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

const LoggedInRoutes = [
  <Route path="https://stackoverflow.com/" component={Profile} />
];

const LoggedOutRoutes = [
  <Route path="https://stackoverflow.com/" component={Login} />
];

  • Esto es muy simple, lo cual es bueno. La cuestión es que, por lo general, desea reconocer las mismas rutas si está desconectado o conectado, por lo que puede redirigir correctamente para iniciar sesión si el usuario cerró la sesión. Por lo general, desea que las rutas sean las mismas, pero que se comporten de manera diferente según el estado de inicio de sesión. Además, con su solución está agregando duplicación, al crear la misma ruta en 2 ubicaciones diferentes, lo que es más difícil de mantener.

    –Rafael Porras Lucena

    1 de abril de 2020 a las 13:21


Si desea utilizar la autenticación en toda su aplicación, debe almacenar algunos datos en toda la aplicación (por ejemplo, token). Puede configurar dos mixins React que son responsables de administrar $auth objeto. Este objeto no debería estar disponible fuera de esos dos mixins. Aquí hay un ejemplo de eso:

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();
                    }
                }
            }
        }
    };
});

Entonces puedes simplemente mezclar Authenticator mezclar con sus componentes de inicio de sesión (pantalla de inicio de sesión, ventana emergente de inicio de sesión, etc.) y llamar this.login función cuando tenga todos los datos necesarios.

Lo más importante es proteger sus componentes mezclándolos NeedsAuthenticatedUser mezclando Cada componente que necesita un usuario autenticado tendrá que verse así:

var um = require('userManagement');

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

Tenga en cuenta que NeedsAuthenticatedUser utiliza la API de enrutador de reacción (willTransitionTo y transition.abort()).

Puede usar HOC y auth es una variable que puede cambiar el valor verdadero o falso (autorización)

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

ruta-privada.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 }
      }} />
  )} />;
}

Uso:

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

Residencia en https://tylermcginnis.com/react-router-protected-routes-authentication/.

Puede evitar representar el componente antes de confirmar la autenticación, como se muestra a continuación:

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 />}
        </>
    )
};

O simplemente, yarn add react-routersqué componente tiene accesorios beforeEach, beforeRoute como Vue Route.

¿Ha sido útil esta solución?