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

81

¿Alguien sabe cómo restringir el acceso a rutas particulares en react-router? Quiero comprobar si el usuario está conectado 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 mis <Route>componentes, o debería manejarlo dentro de mis controladores de componentes?

<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>
Tanner Semerad
fuente
Si no han iniciado sesión, redirigir al controlador de inicio de sesión. También tenga en cuenta que el cliente tiene acceso a todos los JS que carga, así que no almacene información confidencial en él.
Coronel Treinta y Dos
@Tanner Semerad, ¿tienes algún repositorio de github sobre cómo lograste esto brevemente?
jit
@jit No lo siento, lo siento. La respuesta de miciek a continuación fue lo que necesitaba, pero tenga en cuenta que esto fue antes de react-router 1.0. Sé que varias cosas han cambiado desde que se lanzó la versión 1.0, pero en general es similar.
Tanner Semerad
La respuesta de @ jayair es lo que estoy usando ahora, y funciona muy bien
Tanner Semerad

Respuestas:

93

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 una Authbiblioteca para verificar si el usuario está autenticado actualmente. Reemplace esto con su función de verificación de autenticación. Si es así, establecemos la isAuthenticatedbandera en 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 AuthenticatedRoutey UnauthenticatedRoute.

Se AuthenticatedRoute.jsve así.

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 isAuthenticatedestá configurado en true. Si es así, renderizará el componente deseado. De lo contrario, redirigirá a la página de inicio de sesión.

El UnauthenticatedRoute.jspor otro lado se ve así.

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

En este caso, si isAuthenticatedestá configurado en false, renderizará el componente deseado. Y si está configurado como 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 el equipo de React considera que los Mixins son dañinos ( https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html ).

Si alguien se encuentra con esta pregunta y está buscando la forma recomendada de hacerlo, le sugiero que use Componentes de orden superior en lugar de Mixins.

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

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 en sus rutas. En el caso de su 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

jayair
fuente
Si mi código original estaba usando <Route getComponent = {myAsyncComponentGenerator}>, ¿cómo lo haría funcionar con este ejemplo?
Bran
Tengo un código muy similar, pero mi pregunta es, ¿está lo suficientemente seguro? Es decir puede ser un atacante puede cambiar el código JS minified tal que la sustitución this.props.isLoggedIncon truey entrada de derivación?
karim elhelawy
4
@karimelhelawy Eso es cierto y por eso necesitas hacer cumplir la autenticación en la API de tu servidor.
cbr
7
<Route handler={}/>está en desuso en v1.0, debe usar <Route component={} />.
Conocimiento
1
componentWillMountpronto quedarán obsoletos. Léalo en la publicación del blog en reactjs.org . En su lugar, iría con la respuesta que @jacob proporcionó.
Tom
28

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

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

<Route exact path="/" render={() => (
  loggedIn ? (
    <Redirect to="/dashboard"/>
  ) : (
    <PublicHomePage/>
  )
)}/>
jacob
fuente
¿Cómo puedo utilizar "logIn" como función o variable ?. ¿Puedes explicarlo un poco
Kunvar Singh
@KunvarSingh probablemente debería ser una función porque el valor cambia.
jacob
2

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 de React que sean responsables de administrar el $authobjeto. 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();
                    }
                }
            }
        }
    };
});

Luego, puede simplemente Authenticatormezclar la mezcla con sus componentes de inicio de sesión (pantalla de inicio de sesión, ventana emergente de inicio de sesión, etc.) y llamar a la this.loginfunción cuando tenga todos los datos necesarios.

Lo más importante es proteger sus componentes mezclándolos con NeedsAuthenticatedUsermixin. 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 NeedsAuthenticatedUserutiliza la API react-router ( willTransitionToy transition.abort()).

Michał Płachta
fuente
2
Los mixins son una mala idea para seguir adelante. Leer más
boldnik
Una forma mucho mejor que encontré: github.com/reactjs/react-router/tree/master/examples/auth-flow
boldnik
1
Los mixins se han eliminado de ES6 y React los ha desaprobado.
Muelle
2

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 le pase el loggedInaccesorio):

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
fuente
Esto es muy simple, lo cual es bueno. Lo que pasa es que, por lo general, desea reconocer las mismas rutas si está desconectado o conectado, de modo que pueda redirigir correctamente para iniciar sesión si el usuario se desconectó. Por lo general, desea que las rutas sean iguales, pero que se comporten de manera diferente según el estado de inicio de sesión. También con su solución está agregando duplicación, creando la misma Ruta en 2 ubicaciones diferentes que es más difícil de mantener.
Rafael Porras Lucena
1

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

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

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

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

Ambroise Rabier
fuente
-2

Por lo general, a un usuario que haya iniciado sesión se le otorgará un token y utilizará este token para cualquier comunicación con el servidor. Lo que solemos hacer es definir una página raíz y las cosas se construyen sobre esa página. esta página raíz realiza la localización, autenticación y otras configuraciones por usted.

aquí hay un ejemplo

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

en su página raíz, verifique que el token sea nulo o autentique el token con el servidor para ver si el usuario tiene un inicio de sesión válido.

espero que esto ayude :)

Jim
fuente
2
Bien, entonces, ¿cómo podría detener la importación de la clase "Descripción general" si Auth no se ha procesado, o cómo se ve el controlador "Principal"? Por ejemplo, ¿qué pasa si "Descripción general" tiene una dependencia que requiere una aplicación autenticada para ejecutarse? Debido a que se importa para ejecutarse en el enrutador, todas sus dependencias también se habrán importado y, por lo tanto, tiene una aplicación rota, ¿verdad?
Marais Rossouw
Esto no responde a la pregunta que se hizo
HermannHH