Jak ograniczyć dostęp do tras w React-routerze?

83

Czy ktoś wie, jak ograniczyć dostęp do określonych tras w routerze reagującym? Chcę sprawdzić, czy użytkownik jest zalogowany przed zezwoleniem na dostęp do określonej trasy. Myślałem, że to będzie proste, ale dokumentacja nie jest jasna, jak to zrobić.

Czy jest to coś, co powinienem skonfigurować, gdzie definiuję moje <Route>komponenty, czy powinienem obsługiwać to w moich programach obsługi składników?

<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
źródło
Jeśli nie są zalogowani, przekieruj do modułu obsługi logowania. Pamiętaj również, że klient ma dostęp do wszystkich ładowanych plików JS, więc nie przechowuj w nich poufnych informacji.
pułkownik trzydzieści dwa
@Tanner Semerad czy masz jakieś repozytorium github o tym, jak to krótko osiągnąłeś.
jit
@jit Nie, przepraszam. Odpowiedź z miciek poniżej była tym, czego potrzebowałem, ale pamiętaj, że było to przed reakcją routera 1.0. Wiem, że od czasu wydania 1.0 zmieniło się wiele rzeczy, ale jest w większości podobnie.
Tanner Semerad
Odpowiedź @ jayair jest tym, czego używam teraz i działa świetnie
Tanner Semerad

Odpowiedzi:

94

Aktualizacja (16 sierpnia 2019 r.)

W React-router v4 i korzystaniu z React Hooks wygląda to trochę inaczej. Zacznijmy od twojego 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>
  );
}

Używamy Authbiblioteki, aby sprawdzić, czy użytkownik jest aktualnie uwierzytelniony. Zastąp to funkcją sprawdzania autoryzacji. Jeśli tak, ustawiamy isAuthenticatedflagę na true. Robimy to po pierwszym załadowaniu naszej aplikacji. Warto również wspomnieć, że możesz chcieć dodać znak ładowania do swojej aplikacji podczas sprawdzania autoryzacji, aby nie flashować strony logowania za każdym razem, gdy odświeżasz stronę.

Następnie przekazujemy flagę na nasze trasy. Tworzymy dwa rodzaje tras AuthenticatedRoutei UnauthenticatedRoute.

Do AuthenticatedRoute.jswygląda następująco.

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

Sprawdza, czy isAuthenticatedjest ustawiona na true. Jeśli tak, wyrenderuje żądany komponent. Jeśli nie, przekieruje do strony logowania.

Z UnauthenticatedRoute.jsdrugiej strony tak wygląda.

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

W tym przypadku, jeśli isAuthenticatedjest ustawiona na false, wyrenderuje żądany komponent. A jeśli jest ustawiona na true, przekieruje Cię na stronę główną.

Szczegółowe wersje tego można znaleźć w naszym przewodniku - https://serverless-stack.com/chapters/create-a-route-that-redirects.html .

Starsza wersja

Zaakceptowana odpowiedź jest prawidłowa, ale Mixins są uważane za szkodliwe ( https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html ) przez zespół React.

Jeśli ktoś napotka to pytanie i szuka zalecanego sposobu, aby to zrobić, sugerowałbym użycie komponentów wyższego rzędu zamiast miksów.

Oto przykład HOC, który sprawdza, czy użytkownik jest zalogowany przed kontynuowaniem. A jeśli użytkownik nie jest zalogowany, przekieruje Cię do strony logowania. Ten komponent przyjmuje właściwość o nazwie isLoggedIn, która jest w zasadzie flagą, którą aplikacja może przechowywać, aby wskazać, czy użytkownik jest zalogowany.

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

Aby skorzystać z tego HOC, po prostu owiń go wokół swoich tras. W przypadku twojego przykładu byłoby to:

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

Omawiam to i kilka innych tematów w szczegółowym samouczku krok po kroku tutaj - https://serverless-stack.com/chapters/create-a-hoc-that-checks-auth.html

jayair
źródło
Jeśli mój oryginalny kod korzystał z <Route getComponent = {myAsyncComponentGenerator}>, jak mam to zadziałać w tym przykładzie?
Bran
Mam bardzo podobny kod, ale moje pytanie brzmi: czy jest wystarczająco zabezpieczony? Mam na myśli to, że może atakujący może zmienić zminimalizowany kod JS w taki sposób, że zamieniając this.props.isLoggedIngo na truei ominął logowanie?
karim elhelawy
4
@karimelhelawy To prawda iz tego powodu musisz wymusić uwierzytelnianie w API swojego serwera.
cbr
7
<Route handler={}/>jest przestarzały w wersji 1.0, powinieneś użyć <Route component={} />.
Wiedza
1
componentWillMountwkrótce zostaną wycofane. Przeczytaj to w poście na blogu na stronie internetowej actjs.org . Zamiast tego skorzystam z odpowiedzi podanej przez @jacob.
Tom
29

Jest (teraz?) Tego przykład w dokumentacji React Router 4 dla Redirect

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

<Route exact path="/" render={() => (
  loggedIn ? (
    <Redirect to="/dashboard"/>
  ) : (
    <PublicHomePage/>
  )
)}/>
Jakub
źródło
Jak mogę użyć „zalogowanego” jako funkcji lub zmiennej ?. czy możesz to trochę wyjaśnić
Kunvar Singh
@KunvarSingh prawdopodobnie powinna to być funkcja, ponieważ zmienia się wartość.
jacob
3

react-router zachęca do deklaratywnego podejścia do routera, powinieneś uczynić router tak głupim, jak to tylko możliwe i unikać umieszczania logiki routingu w komponentach.

Oto, jak możesz to zrobić (zakładając, że przekażesz to loggedInrekwizyt):

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
źródło
To bardzo proste, co jest dobre. Chodzi o to, że zwykle chcesz rozpoznać te same trasy, jeśli jesteś wylogowany lub zalogowany, więc możesz poprawnie przekierować do logowania, jeśli użytkownik został wylogowany. Zwykle chcesz, aby trasy były takie same, ale zachowywały się w inny sposób w zależności od statusu zalogowania. Również w przypadku rozwiązania dodajesz duplikację, tworząc tę ​​samą trasę w 2 różnych lokalizacjach, które są trudniejsze w utrzymaniu.
Rafael Porras Lucena
2

Jeśli chcesz używać uwierzytelniania w całej aplikacji, musisz przechowywać pewne dane w całej aplikacji (np. Token). Możesz skonfigurować dwa miksy React, które są odpowiedzialne za zarządzanie $authobiektem. Ten obiekt nie powinien być dostępny poza tymi dwoma mikserami. Oto przykład:

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

Następnie możesz po prostu Authenticatormieszać ze swoimi komponentami logowania (ekran logowania, wyskakujące okienko logowania itp.) I wywoływać this.loginfunkcję, gdy masz wszystkie niezbędne dane.

Najważniejszą rzeczą jest ochrona składników poprzez ich domieszanie NeedsAuthenticatedUser. Każdy komponent, który potrzebuje uwierzytelnionego użytkownika, będzie musiał wyglądać tak:

var um = require('userManagement');

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

Zauważ, że NeedsAuthenticatedUserużywa funkcji API React-router ( willTransitionToi transition.abort()).

Michał Płachta
źródło
2
Mixiny to zły pomysł, aby przejść dalej. Czytaj więcej
boldnik
O wiele lepszy sposób, który znalazłem: github.com/reactjs/react-router/tree/master/examples/auth-flow
boldnik
1
Mixiny zostały usunięte z ES6, a React wycofuje je.
Przystań
1

Możesz użyć HOC, a auth to zmienna, którą możesz zmienić wartość true lub false oznacza (autoryzacja)

<Route path="/login" component={SignIn} />
<Route path="/posts" render = {() => (auth ?  (<Post />) : (<Redirect to="/login" />))}/>
Ankit Kumar Rajpoot
źródło
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 }
      }} />
  )} />;
}

Stosowanie:

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

Na podstawie https://tylermcginnis.com/react-router-protected-routes-authentication/ .

Ambroise Rabier
źródło
-2

zwykle zalogowany użytkownik otrzyma token i używa go do komunikacji z serwerem. To, co zwykle robimy, to definiowanie strony głównej, a rzeczy są tworzone na podstawie tej strony. ta strona główna wykonuje dla Ciebie lokalizację, uwierzytelnianie i inne konfiguracje.

oto przykład

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

na swojej stronie głównej sprawdź, czy token nie ma wartości null lub uwierzytelnij token na serwerze, aby sprawdzić, czy użytkownik ma poprawny login.

mam nadzieję że to pomoże :)

Jim
źródło
2
Dobrze, więc jak mogę zatrzymać importowanie klasy „Overview”, jeśli uwierzytelnianie nie przeszło, lub jak wygląda procedura obsługi „Main”? Na przykład, co się stanie, jeśli „Przegląd” ma zależność, która wymaga do uruchomienia uwierzytelnionej aplikacji? Ponieważ jest importowany do działania na routerze, wszystkie jego zależności również zostaną zaimportowane, więc masz zepsutą aplikację, prawda?
Marais Rossouw
To nie odpowiada na zadane pytanie
HermannHH