web-dev-qa-db-ja.com

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)のドキュメントにあります、それはのような何かを示唆しています

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

render()でアクションをディスパッチするのは正しくありません。 componentDidMountや他のフックのどちらでも本当に正しいようには見えませんか?

87
Jiew Meng

あなたはRedirectコンポーネントを使いたいでしょう。この問題に対するいくつかの異なるアプローチがあります。これは私が好きなものです、authed小道具を取り込んで、その小道具に基づいてレンダリングするPrivateRouteコンポーネントがあります。

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

今すぐあなたのRoutesはこのように見えることができます

<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による保護されたルートと認証

170
Tyler McGinnis

Tnx Tyler McGinnisによる解決策私は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" />
15
MrDuDuDu

react-router-domをインストールする

次に、有効ユーザー用と無効ユーザー用に2つのコンポーネントを作成します。

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
Jose G Varanam

問題に私の解決策を追加するだけです。

私は認証のためにjwtトークンを使っています、それでユーザーがそのトークンを持っているなら私はそれらをホームページにリダイレクトするでしょう、そうでなければ私はデフォルトでサインインページにリダイレクトしますユーザーがログインしたら、サインインページのURL(私の場合は/)にアクセスしてみてください。デフォルトでそれらを家にリダイレクトします( '/ home')。

そして私のコンポーネントはユーザートークンが有効かどうかをチェックするためにrequireAuthという名前のHOCを持っています。

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')
);
3
Hemanthvrm

@ 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>
2
Felipe Augusto

私はそれがしばらく経っていたことを知っています、しかし私は私用および公共の経路のために 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"/>

私はそれが役立つことを願っています!

2
Gonzalo Cañada

私の前の答えはスケーラブルではありません。これが私が良いアプローチだと思うものです -

あなたのルート -

<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小道具でラッパーを使うことです。そして、それはauthが必要でないか、すでに認証されているならオリジナルのコンポーネントを返します、そうでなければデフォルトのコンポーネントを返しますログイン。

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
  };
};
1
Varun Kumar

私は使用して実装しました -

<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's 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

1
Varun Kumar

それはあなたのためらいがあなた自身のコンポーネントを作成してからrenderメソッドでディスパッチすることにあるようですか? <Route>コンポーネントのrenderメソッドを使うだけで両方を避けることができます。本当に必要でなければ、<AuthenticatedRoute>コンポーネントを作成する必要はありません。それは以下のように簡単にすることができます。 {...routeProps}スプレッドに注意して、<Route>コンポーネントのプロパティを引き続き子コンポーネント(この場合は<MyComponent>)に送信してください。

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

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

} />

React 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
1
Todd Chaffee

私もいくつかの答えを探していました。ここではすべての回答が非常に良いですが、ユーザーがアプリケーションを開いた後にアプリケーションを起動した場合にどのように使用できるかについての回答はありません。 (私はクッキーを一緒に使用すると言いました)。

別の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で保護されたルートを作成する

0
nirmal
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>
  )
}
0
Fellow Stranger

シンプルでクリーンな保護されたルートがあります

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は、基本的にブール値を返す認証トークンをチェックするメソッド呼び出しです。

0
Anupam Maurya