Подтвердить что ты не робот

Как реализовать аутентифицированные маршруты в React Router 4?

Я пытался реализовать аутентифицированные маршруты, но обнаружил, что React Router 4 теперь мешает этому работать:

<Route exact path="/" component={Index} />
<Route path="/auth" component={UnauthenticatedWrapper}>
    <Route path="/auth/login" component={LoginBotBot} />
</Route>
<Route path="/domains" component={AuthenticatedWrapper}>
    <Route exact path="/domains" component={DomainsIndex} />
</Route>

Ошибка:

Предупреждение. Вы не должны использовать <Route component> и <Route children> в том же маршруте; <Route children> будет игнорироваться

В этом случае, каков правильный способ реализовать это?

Он появляется в react-router (v4) docs, он предлагает что-то вроде

<Router>
    <div>
    <AuthButton/>
    <ul>
        <li><Link to="/public">Public Page</Link></li>
        <li><Link to="/protected">Protected Page</Link></li>
    </ul>
    <Route path="/public" component={Public}/>
    <Route path="/login" component={Login}/>
    <PrivateRoute path="/protected" component={Protected}/>
    </div>
</Router>

Но возможно ли это достичь, объединив вместе пучок маршрутов?


UPDATE

Хорошо, после некоторых исследований я придумал следующее:

import React, {PropTypes} from "react"
import {Route} from "react-router-dom"

export default class AuthenticatedRoute extends React.Component {
  render() {
    if (!this.props.isLoggedIn) {
      this.props.redirectToLogin()
      return null
    }
    return <Route {...this.props} />
  }
}

AuthenticatedRoute.propTypes = {
  isLoggedIn: PropTypes.bool.isRequired,
  component: PropTypes.element,
  redirectToLogin: PropTypes.func.isRequired
}

Isit правильно отправить действие в render(), он чувствует себя не так. Это действительно не кажется правильным с помощью componentDidMount или какого-либо другого крючка?

4b9b3361

Ответ 1

Вам понадобится использовать компонент Redirect. Существует несколько разных подходов к этой проблеме. Здесь мне нравится, есть компонент PrivateRoute, который принимает authed prop, а затем отображает на основе этого реквизита.

function PrivateRoute ({component: Component, authed, ...rest}) {
  return (
    <Route
      {...rest}
      render={(props) => authed === true
        ? <Component {...props} />
        : <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
    />
  )
}

Теперь ваш Route может выглядеть примерно так:

<Route path='/' exact component={Home} />
<Route path='/login' component={Login} />
<Route path='/register' component={Register} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />

Если вы все еще смущены, я написал это сообщение, которое может помочь - Защищенные маршруты и аутентификация с помощью React Router v4

Ответ 2

Tnx Tyler McGinnis для решения. Я излагаю свою идею из идеи Тайлера Макгинниса.

const DecisionRoute = ({ trueComponent, falseComponent, decisionFunc, ...rest }) => {
  return (
    <Route
      {...rest}

      render={
        decisionFunc()
          ? trueComponent
          : falseComponent
      }
    />
  )
}

Вы можете реализовать это как

<DecisionRoute path="/signin" exact={true}
            trueComponent={redirectStart}
            falseComponent={SignInPage}
            decisionFunc={isAuth}
          />

decisionFunc - это просто функция, которая возвращает true или false

const redirectStart = props => <Redirect to="/orders" />

Ответ 3

установить реакцию-маршрутизатор

затем создайте два компонента для допустимых пользователей и других для недопустимых пользователей.

попробуйте это на app.js

import React from 'react';

import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect
} from 'react-router-dom';

import ValidUser from "./pages/validUser/validUser";
import InValidUser from "./pages/invalidUser/invalidUser";
const loggedin = false;

class App extends React.Component {
 render() {
    return ( 
      <Router>
      <div>
        <Route exact path="/" render={() =>(
          loggedin ? ( <Route  component={ValidUser} />)
          : (<Route component={InValidUser} />)
        )} />

        </div>
      </Router>
    )
  }
}
export default App;

Ответ 4

Просто добавляю свое решение проблемы.

Я использую токены jwt для аутентификации, поэтому, если у пользователя есть этот токен, я перенаправлю их на домашнюю страницу, иначе я перенаправлю их на страницу входа по умолчанию (это маршрут "/"). Поэтому, как только пользователь вошел в систему и попытался получить доступ к URL страницы входа (в моем случае '/'). По умолчанию я перенаправлю их на дом ('/home').

И у моих компонентов есть HOC с именем requireAuth, чтобы проверить, является ли токен пользователя действительным. Если нет, то вызовите действие выхода, которое удаляет токен локальной истории.

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch, Redirect  } from 'react-router-dom';  

//and also import appropriate components

//middleware

  class checkStatus extends React.Component {
        render() {
              if(localStorage.getItem('token')){
                return (
                  <Fragment>
                    <App>
                      <Route path="/home" exact component={Overview} />
                      <Route path="/home/add" exact component={Add} />
                      <Route path="/signout" component={Signout} />
                      <Route path="/details" component={details} />
                      <Route exact path="/" render={() => <Redirect to="/home" />} />
                    </App>

                </Fragment>
                )
              }else{
                return (
                  <Fragment>
                    <Route path="/" exact component={Signin} />
                    <Redirect to="/"  />
                  </Fragment>
                )
              }
         } }

    ReactDOM.render(   <Provider store={store}>
        <BrowserRouter>
          <Switch >
              <Route path="/" exact component={checkStatus} />
              <Route path="/:someParam"  component={checkStatus}/>
          </Switch >
        </BrowserRouter>   </Provider>,   document.querySelector('#root')
);

Ответ 5

Я знаю, что это было какое-то время, но я работал над пакетом npm для частных и общедоступных маршрутов.

Здесь, как сделать частный маршрут:

<PrivateRoute exact path="/private" authed={true} redirectTo="/login" component={Title} text="This is a private route"/>

И вы также можете создавать общедоступные маршруты, доступ к которым можно получить только у неавторизованного пользователя.

<PublicRoute exact path="/public" authed={false} redirectTo="/admin" component={Title} text="This route is for unauthed users"/>

Надеюсь, это поможет!

Ответ 6

Основано на ответе @Tyler McGinnis. Я сделал другой подход, используя синтаксис ES6 и вложенные маршруты с обернутыми компонентами:

import React, { cloneElement, Children } from 'react'
import { Route, Redirect } from 'react-router-dom'

const PrivateRoute = ({ children, authed, ...rest }) =>
  <Route
    {...rest}
    render={(props) => authed ?
      <div>
        {Children.map(children, child => cloneElement(child, { ...child.props }))}
      </div>
      :
      <Redirect to={{ pathname: '/', state: { from: props.location } }} />}
  />

export default PrivateRoute

И используя это:

<BrowserRouter>
  <div>
    <PrivateRoute path='/home' authed={auth}>
      <Navigation>
        <Route component={Home} path="/home" />
      </Navigation>
    </PrivateRoute>

    <Route exact path='/' component={PublicHomePage} />
  </div>
</BrowserRouter>

Ответ 7

Кажется, ваша нерешительность заключается в создании вашего собственного компонента, а затем в методе рендеринга? Ну, вы можете избежать как с помощью метода render компонента <Route>. Не нужно создавать компонент <AuthenticatedRoute>, если вы этого не хотите. Это может быть так просто, как показано ниже. Обратите внимание на распространение {...routeProps}, чтобы вы продолжали отправлять свойства компонента <Route> до дочернего компонента (<MyComponent> в этом случае).

<Route path='/someprivatepath' render={routeProps => {

   if (!this.props.isLoggedIn) {
      this.props.redirectToLogin()
      return null
    }
    return <MyComponent {...routeProps} anotherProp={somevalue} />

} />

См. Документация по устранению Router V4

Если вы хотите создать выделенный компонент, то похоже, что вы на правильном пути. Так как React Router V4 является чисто декларативной маршрутизацией (он так говорит в описании), я не думаю, что вам удастся сместить код перенаправления за пределы обычного жизненного цикла компонента. Глядя на код для самого React Router, они выполняют перенаправление в componentWillMount или componentDidMount в зависимости от того, является ли он сервером боковой рендеринг. Вот приведенный ниже код, который довольно прост и может помочь вам чувствовать себя более комфортно с тем, где поставить логику перенаправления.

import React, { PropTypes } from 'react'

/**
 * The public API for updating the location programatically
 * with a component.
 */
class Redirect extends React.Component {
  static propTypes = {
    push: PropTypes.bool,
    from: PropTypes.string,
    to: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.object
    ])
  }

  static defaultProps = {
    push: false
  }

  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.shape({
        push: PropTypes.func.isRequired,
        replace: PropTypes.func.isRequired
      }).isRequired,
      staticContext: PropTypes.object
    }).isRequired
  }

  isStatic() {
    return this.context.router && this.context.router.staticContext
  }

  componentWillMount() {
    if (this.isStatic())
      this.perform()
  }

  componentDidMount() {
    if (!this.isStatic())
      this.perform()
  }

  perform() {
    const { history } = this.context.router
    const { push, to } = this.props

    if (push) {
      history.push(to)
    } else {
      history.replace(to)
    }
  }

  render() {
    return null
  }
}

export default Redirect

Ответ 8

Я реализовал с помощью -

<Route path='/dashboard' render={() => (
    this.state.user.isLoggedIn ? 
    (<Dashboard authenticate={this.authenticate} user={this.state.user} />) : 
    (<Redirect to="/login" />)
)} />
Аутентификация реквизитов

будет передаваться компонентам, например. регистрация, с помощью которой пользовательское состояние может быть изменено. Завершить AppRoutes -

import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';

import Home from '../pages/home';
import Login from '../pages/login';
import Signup from '../pages/signup';
import Dashboard from '../pages/dashboard';

import { config } from '../utils/Config';

export default class AppRoutes extends React.Component {

    constructor(props) {
        super(props);

        // initially assuming that user is logged out
        let user = {
            isLoggedIn: false
        }

        // if user is logged in, his details can be found from local storage
        try {
            let userJsonString = localStorage.getItem(config.localStorageKey);
            if (userJsonString) {
                user = JSON.parse(userJsonString);
            }
        } catch (exception) {
        }

        // updating the state
        this.state = {
            user: user
        };

        this.authenticate = this.authenticate.bind(this);
    }

    // this function is called on login/logout
    authenticate(user) {
        this.setState({
            user: user
        });

        // updating user details
        localStorage.setItem(config.localStorageKey, JSON.stringify(user));
    }

    render() {
        return (
            <Switch>
                <Route exact path='/' component={Home} />
                <Route exact path='/login' render={() => <Login authenticate={this.authenticate} />} />
                <Route exact path='/signup' render={() => <Signup authenticate={this.authenticate} />} />
                <Route path='/dashboard' render={() => (
                    this.state.user.isLoggedIn ? 
                            (<Dashboard authenticate={this.authenticate} user={this.state.user} />) : 
                            (<Redirect to="/login" />)
                )} />
            </Switch>
        );
    }
} 

Проверьте полный проект здесь: https://github.com/varunon9/hello-react

Ответ 9

Мой предыдущий ответ не масштабируется. Вот то, что я считаю хорошим approach-

Ваш Routes-

<Switch>
  <Route
    exact path="/"
    component={matchStateToProps(InitialAppState, {
      routeOpen: true // no auth is needed to access this route
    })} />
  <Route
    exact path="/profile"
    component={matchStateToProps(Profile, {
      routeOpen: false // can set it false or just omit this key
    })} />
  <Route
    exact path="/login"
    component={matchStateToProps(Login, {
      routeOpen: true
    })} />
  <Route
    exact path="/forgot-password"
    component={matchStateToProps(ForgotPassword, {
      routeOpen: true
    })} />
  <Route
    exact path="/dashboard"
    component={matchStateToProps(DashBoard)} />
</Switch>

Идея состоит в том, чтобы использовать оболочку в подпорках component которая будет возвращать исходный компонент, если аутентификация не требуется или уже аутентифицирована, в противном случае будет возвращаться компонент по умолчанию, например, Login.

const matchStateToProps = function(Component, defaultProps) {
  return (props) => {
    let authRequired = true;

    if (defaultProps && defaultProps.routeOpen) {
      authRequired = false;
    }

    if (authRequired) {
      // check if loginState key exists in localStorage (Your auth logic goes here)
      if (window.localStorage.getItem(STORAGE_KEYS.LOGIN_STATE)) {
        return <Component { ...defaultProps } />; // authenticated, good to go
      } else {
        return <InitialAppState { ...defaultProps } />; // not authenticated
      }
    }
    return <Component { ...defaultProps } />; // no auth is required
  };
};

Ответ 10

const Root = ({ session }) => {
  const isLoggedIn = session && session.getCurrentUser
  return (
    <Router>
      {!isLoggedIn ? (
        <Switch>
          <Route path="/signin" component={<Signin />} />
          <Redirect to="/signin" />
        </Switch>
      ) : (
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/about" component={About} />
          <Route path="/something-else" component={SomethingElse} />
          <Redirect to="/" />
        </Switch>
      )}
    </Router>
  )
}

Ответ 11

Вы можете использовать response-router-guard, он поддерживает маршрут конфигурации объекта, разбиение кода и маршрут аутентификации.

Ответ 12

Вот простой чистый защищенный маршрут

const ProtectedRoute 
  = ({ isAllowed, ...props }) => 
     isAllowed 
     ? <Route {...props}/> 
     : <Redirect to="/authentificate"/>;
const _App = ({ lastTab, isTokenVerified })=> 
    <Switch>
      <Route exact path="/authentificate" component={Login}/>
      <ProtectedRoute 
         isAllowed={isTokenVerified} 
         exact 
         path="/secrets" 
         component={Secrets}/>
      <ProtectedRoute 
         isAllowed={isTokenVerified} 
         exact 
         path="/polices" 
         component={Polices}/>
      <ProtectedRoute 
         isAllowed={isTokenVerified} 
         exact 
         path="/grants" component={Grants}/>
      <Redirect from="/" to={lastTab}/>
    </Switch>

isTokenVerified - это вызов метода для проверки токена авторизации, в основном он возвращает логическое значение.

Ответ 13

Я также искал ответ. Здесь все ответы довольно хороши, но ни один из них не дает ответов, как мы можем использовать его, если пользователь запускает приложение после его открытия. (Я хотел сказать, используя cookie вместе).

Не нужно создавать даже другой компонент PrivateRoute. Ниже мой код

    import React, { Component }  from 'react';
    import { Route, Switch, BrowserRouter, Redirect } from 'react-router-dom';
    import { Provider } from 'react-redux';
    import store from './stores';
    import requireAuth from './components/authentication/authComponent'
    import SearchComponent from './components/search/searchComponent'
    import LoginComponent from './components/login/loginComponent'
    import ExampleContainer from './containers/ExampleContainer'
    class App extends Component {
    state = {
     auth: true
    }


   componentDidMount() {
     if ( ! Cookies.get('auth')) {
       this.setState({auth:false });
     }
    }
    render() {
     return (
      <Provider store={store}>
       <BrowserRouter>
        <Switch>
         <Route exact path="/searchComponent" component={requireAuth(SearchComponent)} />
         <Route exact path="/login" component={LoginComponent} />
         <Route exact path="/" component={requireAuth(ExampleContainer)} />
         {!this.state.auth &&  <Redirect push to="/login"/> }
        </Switch>
       </BrowserRouter>
      </Provider>);
      }
     }
    }
    export default App;

А вот и AuthComponent

import React  from 'react';
import { withRouter } from 'react-router';
import * as Cookie from "js-cookie";
export default function requireAuth(Component) {
class AuthenticatedComponent extends React.Component {
 constructor(props) {
  super(props);
  this.state = {
   auth: Cookie.get('auth')
  }
 }
 componentDidMount() {
  this.checkAuth();
 }
 checkAuth() {
  const location = this.props.location;
  const redirect = location.pathname + location.search;
  if ( ! Cookie.get('auth')) {
   this.props.history.push('/login?redirect=${redirect}');
  }
 }
render() {
  return Cookie.get('auth')
   ? <Component { ...this.props } />
   : null;
  }
 }
 return  withRouter(AuthenticatedComponent)
}

Ниже я написал блог, вы также можете получить более подробное объяснение.

Создание защищенных маршрутов в ReactJS