web-dev-qa-db-fra.com

Comment implémenter des routes authentifiées dans React Router 4?

J'essayais d'implémenter des itinéraires authentifiés mais j'ai constaté que React Router 4 empêche maintenant ce fonctionnement: 

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

L'erreur est: 

Attention: vous ne devez pas utiliser <Route component> et <Route children> dans le même itinéraire; <Route children> sera ignoré

Dans ce cas, quelle est la bonne façon de mettre en œuvre cela? 

Il apparaît dans la documentation react-router (v4), il suggère quelque chose comme 

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

Mais est-il possible de réaliser cela en regroupant plusieurs itinéraires? 


METTRE &AGRAVE; JOUR

Ok, après quelques recherches, j'ai trouvé ceci: 

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
}

Il est correct d'envoyer une action dans render(). Cela ne semble pas vraiment correct avec componentDidMount ou un autre crochet non plus? 

72
Jiew Meng

Vous allez vouloir utiliser le composant Redirect. Il y a quelques approches différentes à ce problème. En voici un que j’aime bien: avoir un composant PrivateRoute qui accepte un accessoire authed, puis le rend en fonction de ces accessoires.

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

Maintenant, votre Routes peut ressembler à quelque chose comme ça

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

Si vous êtes toujours confus, j’ai écrit ce billet qui pourrait aider - Routes protégées et authentification avec React Router v4

147
Tyler McGinnis

Tnx Tyler McGinnis pour la solution ... Je fais mon idée de Tyler McGinnis.

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

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

Vous pouvez implémenter ça comme ça

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

decisionFunc juste une fonction qui renvoie true ou false

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

installer react-router-dom

créez ensuite deux composants, l'un pour les utilisateurs valides et l'autre pour les utilisateurs non valides.

essayez ceci sur 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

Juste en ajoutant ma solution au problème.

J'utilise des jetons JWT pour l'authentification. Donc, si l'utilisateur a ce jeton, je les redirigerai vers la page d'accueil ou bien je les redirigerai vers la page de connexion par défaut (qui est cette route '/'). Donc, une fois que l'utilisateur est connecté et tente d'accéder à l'URL de la page de connexion (dans mon cas, '/'). Je les redirigerai vers la maison par défaut ('/ home').

Et mes composants ont un HOC nommé requireAuth pour vérifier si le jeton de l'utilisateur est valide. Si ce n'est pas le cas, appelez l'action de déconnexion qui supprime le jeton d'historique local.

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

Je sais que cela fait longtemps, mais je travaille sur un paquet npm pour les routes privées et publiques.

Voici comment faire un itinéraire privé:

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

Et vous pouvez également créer des itinéraires publics auxquels seuls les utilisateurs non autorisés peuvent accéder.

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

J'espère que ça aide!

2
Gonzalo Cañada

Il semble que votre hésitation soit dans la création de votre propre composant, puis dans l'envoi de la méthode de rendu? Vous pouvez éviter les deux en utilisant simplement la méthode render du composant <Route>. Pas besoin de créer un composant <AuthenticatedRoute> à moins que vous ne le souhaitiez vraiment. Cela peut être aussi simple que ci-dessous. Notez l'étalement {...routeProps} en vous assurant de continuer à envoyer les propriétés du composant <Route> au composant enfant (<MyComponent> dans ce cas).

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

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

} />

Voir la documentation de rendu de React Router V4 } _

Si vous voulez créer un composant dédié, il semble que vous êtes sur la bonne voie. Étant donné que React Router V4 est un routage purement déclaratif (il est dit exactement dans la description), je ne pense pas que vous puissiez vous en sortir en mettant votre code de redirection en dehors du cycle de vie normal du composant. En regardant le code de React Router lui-même , ils effectuent la redirection dans componentWillMount ou componentDidMount en fonction du rendu côté serveur ou non. Voici le code ci-dessous, qui est assez simple et pourrait vous aider à vous sentir plus à l'aise avec l'endroit où placer votre logique de redirection.

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

Basé sur la réponse de @Tyler McGinnis . J'ai adopté une approche différente en utilisant ES6 syntax et nested routes avec des composants encapsulés:

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

Et en l'utilisant:

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

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

Je cherchais aussi une réponse. Ici, toutes les réponses sont assez bonnes, mais aucune d’entre elles n’indique comment nous pouvons l’utiliser si l’utilisateur lance l’application après l’avoir ouverte. (Je voulais dire utiliser un cookie ensemble).

Pas besoin de créer même un composant privé privé différent. Ci-dessous mon code

    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;

Et voici 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)
}

Ci-dessous, j'ai écrit un blog, vous pouvez également obtenir des explications plus détaillées.

Créer des routes protégées dans ReactJS

0
nirmal

Ma réponse précédente n'est pas évolutive. Voici ce que je pense être une bonne approche-

Vos itinéraires

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

L'idée est d'utiliser un wrapper dans les propriétés component qui renverraient le composant d'origine si aucune autorisation n'est requise ou déjà authentifié, sinon le composant par défaut, par exemple. S'identifier.

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

J'ai implémenté using-

<Route path='/dashboard' render={() => (
    this.state.user.isLoggedIn ? 
    (<Dashboard authenticate={this.authenticate} user={this.state.user} />) : 
    (<Redirect to="/login" />)
)} />

les objets d'authentification seront transmis aux composants, par ex. inscription en utilisant quel état d'utilisateur peut être changé. Complete 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>
        );
    }
} 

Vérifiez le projet complet ici: https://github.com/varunon9/hello-react

0
Varun Kumar
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

Voici l'itinéraire simple, propre et protégé

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 est un appel de méthode pour vérifier le jeton d'autorisation. En principe, il retourne un booléen.

0
Anupam Maurya