Wie kann der Zugriff auf Routen im React-Router eingeschränkt werden?

83

Weiß jemand, wie man den Zugriff auf bestimmte Routen im React-Router einschränkt? Ich möchte überprüfen, ob der Benutzer angemeldet ist, bevor ich den Zugriff auf eine bestimmte Route zulasse. Ich dachte, es wäre einfach, aber die Dokumente sind nicht klar, wie es geht.

Ist dies etwas, das ich einrichten sollte, wo ich meine <Route>Komponenten definiere , oder sollte ich es in meinen Komponentenhandlern behandeln?

<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
quelle
Wenn sie nicht angemeldet sind, leiten Sie zum Login-Handler weiter. Beachten Sie auch, dass der Client Zugriff auf alle von ihm geladenen JS hat. Speichern Sie daher keine vertraulichen Informationen darin.
Oberst
@Tanner Semerad Haben Sie ein Github-Repository darüber, wie Sie dies kurz erreicht haben?
Jit
@jit Ich nicht, sorry. Die Antwort von miciek unten war genau das, was ich brauchte, aber denken Sie daran, dass dies vor React-Router 1.0 war. Ich weiß, dass sich seit der Veröffentlichung von 1.0 einige Dinge geändert haben, aber es ist größtenteils ähnlich.
Tanner Semerad
@ Jayair Antwort ist, was ich jetzt benutze, und es funktioniert großartig
Tanner Semerad

Antworten:

94

Update (16. August 2019)

In React-Router v4 und mit React Hooks sieht dies etwas anders aus. Beginnen wir mit Ihrem 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>
  );
}

Wir verwenden eine AuthBibliothek, um zu überprüfen, ob der Benutzer derzeit authentifiziert ist. Ersetzen Sie dies durch Ihre Auth-Check-Funktion. Wenn ja, setzen wir das isAuthenticatedFlag auf true. Wir tun dies, wenn unsere App zum ersten Mal geladen wird. Erwähnenswert ist auch, dass Sie Ihrer App möglicherweise ein Ladeschild hinzufügen möchten, während die Authentifizierungsprüfung ausgeführt wird, damit Sie die Anmeldeseite nicht jedes Mal flashen, wenn Sie die Seite aktualisieren.

Dann geben wir die Flagge an unsere Routen weiter. Wir erstellen zwei Arten von Routen AuthenticatedRouteund UnauthenticatedRoute.

Das AuthenticatedRoute.jssieht so aus.

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

Es wird geprüft, ob eingestellt isAuthenticatedist true. Wenn dies der Fall ist, wird die gewünschte Komponente gerendert. Wenn nicht, wird zur Anmeldeseite weitergeleitet.

Das UnauthenticatedRoute.jssieht dagegen so aus.

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

In diesem Fall wird die gewünschte Komponente gerendert , wenn auf gesetzt isAuthenticatedist false. Und wenn es auf true gesetzt ist, werden Sie zur Startseite weitergeleitet.

Detaillierte Versionen davon finden Sie in unserem Handbuch - https://serverless-stack.com/chapters/create-a-route-that-redirects.html .

Ältere Version

Die akzeptierte Antwort ist korrekt, aber Mixins werden vom React-Team als schädlich eingestuft ( https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html ).

Wenn jemand auf diese Frage stößt und nach der empfohlenen Methode sucht, würde ich vorschlagen, Komponenten höherer Ordnung anstelle von Mixins zu verwenden.

Hier ist ein Beispiel für ein HOC, das prüft, ob der Benutzer angemeldet ist, bevor Sie fortfahren. Wenn der Benutzer nicht angemeldet ist, werden Sie zur Anmeldeseite weitergeleitet. Diese Komponente verwendet eine Requisite namens isLoggedIn, dh ein Flag, das Ihre Anwendung speichern kann, um anzuzeigen, ob der Benutzer angemeldet ist.

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

Und um dieses HOC zu verwenden, wickeln Sie es einfach um Ihre Routen. In Ihrem Beispiel wäre es:

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

Ich beschreibe dieses und einige andere Themen in einem detaillierten Schritt-für-Schritt-Tutorial hier - https://serverless-stack.com/chapters/create-a-hoc-that-checks-auth.html

Jayair
quelle
Wenn mein ursprünglicher Code <Route getComponent = {myAsyncComponentGenerator}> verwenden würde, wie würde ich ihn mit diesem Beispiel zum Laufen bringen?
Bran
Ich habe einen sehr ähnlichen Code, aber meine Frage ist, ist er ausreichend gesichert? Ich meine, kann ein Angreifer den JS-minimierten Code so ändern, dass er this.props.isLoggedIndurch das trueLogin ersetzt und dieses umgeht ?
Karim Elhelawy
4
@karimelhelawy Das stimmt und aus diesem Grund müssen Sie die Authentifizierung in der API Ihres Servers erzwingen.
cbr
7
<Route handler={}/>ist in v1.0 veraltet, sollten Sie verwenden <Route component={} />.
Wissen
1
componentWillMountwerden bald veraltet sein. Lesen Sie es im Blog-Beitrag auf reactjs.org . Stattdessen würde ich mit der Antwort @jacob gehen.
Tom
28

Es gibt (jetzt?) Ein Beispiel dafür in den Dokumenten von React Router 4 für Redirect

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

<Route exact path="/" render={() => (
  loggedIn ? (
    <Redirect to="/dashboard"/>
  ) : (
    <PublicHomePage/>
  )
)}/>
Jakob
quelle
Wie kann ich "eingeloggt" als Funktion oder Variable verwenden? können Sie es nur ein wenig erklären
Kunvar Singh
@ KunvarSingh sollte es wahrscheinlich eine Funktion sein, da sich der Wert ändert.
Jacob
3

react-router Wenn Sie einen deklarativen Ansatz für Ihren Router empfehlen, sollten Sie Ihren Router so dumm wie möglich gestalten und vermeiden, dass Ihre Routing-Logik in Ihre Komponenten integriert wird.

So können Sie es machen (vorausgesetzt, Sie geben ihm die loggedInRequisite):

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
quelle
Das ist sehr einfach, was gut ist. Die Sache ist, dass Sie normalerweise dieselben Routen erkennen möchten, entweder wenn Sie abgemeldet oder angemeldet sind, sodass Sie ordnungsgemäß zur Anmeldung umleiten können, wenn der Benutzer abgemeldet war. Normalerweise möchten Sie, dass die Routen gleich sind, sich jedoch je nach Anmeldestatus unterschiedlich verhalten. Mit Ihrer Lösung fügen Sie auch Duplikate hinzu, indem Sie dieselbe Route an zwei verschiedenen Orten erstellen, die schwieriger zu warten sind.
Rafael Porras Lucena
2

Wenn Sie die Authentifizierung für Ihre gesamte Anwendung verwenden möchten, müssen Sie einige Daten anwendungsweit speichern (z. B. Token). Sie können zwei React-Mixins einrichten, die für die Verwaltung des $authObjekts verantwortlich sind. Dieses Objekt sollte außerhalb dieser beiden Mixins nicht verfügbar sein. Hier ist ein Beispiel dafür:

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

Dann können Sie einfach das AuthenticatorMischen mit Ihren Anmeldekomponenten (Anmeldebildschirm, Anmelde-Popup usw.) mischen und die this.loginFunktion aufrufen , wenn Sie über alle erforderlichen Daten verfügen.

Das Wichtigste ist, Ihre Komponenten durch Einmischen zu schützen NeedsAuthenticatedUser. Jede Komponente, die einen authentifizierten Benutzer benötigt, muss folgendermaßen aussehen:

var um = require('userManagement');

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

Beachten Sie, dass NeedsAuthenticatedUserdie React-Router-API ( willTransitionTound transition.abort()) verwendet wird.

Michał Płachta
quelle
2
Mixins sind eine schlechte Idee, um weiterzumachen. Lesen Sie mehr
boldnik
Ein viel besserer Weg, den ich gefunden habe: github.com/reactjs/react-router/tree/master/examples/auth-flow
boldnik
1
Mixins wurden aus ES6 entfernt und von React abgelehnt.
Pier
1

Sie können HOC verwenden und auth ist eine Variable, mit der Sie den Wert true oder false ändern können (Berechtigung).

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

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

Verwendung:

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

Basierend auf https://tylermcginnis.com/react-router-protected-routes-authentication/ .

Ambroise Rabier
quelle
-2

Normalerweise erhält ein angemeldeter Benutzer ein Token und verwendet dieses Token für die Kommunikation mit dem Server. Normalerweise definieren wir eine Stammseite, und die Dinge bauen auf dieser Seite auf. Diese Stammseite übernimmt die Lokalisierung, Authentifizierung und andere Konfigurationen für Sie.

Hier ist ein Beispiel

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

Überprüfen Sie auf Ihrer Stammseite, ob das Token null ist, oder authentifizieren Sie das Token beim Server, um festzustellen, ob der Benutzer eine gültige Anmeldung hat.

hoffe das hilft :)

Jim
quelle
2
Richtig, wie würde ich den Import der "Übersicht" -Klasse stoppen, wenn Auth nicht durchlaufen wurde, oder wie sieht der "Main" -Handler aus? Was ist beispielsweise, wenn "Übersicht" eine Abhängigkeit aufweist, für deren Ausführung eine authentifizierte App erforderlich ist? Da es importiert wird, um auf dem Router ausgeführt zu werden, wurden auch alle Abhängigkeiten importiert, und Sie haben daher eine kaputte App, oder?
Marais Rossouw
Dies beantwortet nicht die gestellte Frage
HermannHH