159 votes

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

J'essayais d'implémenter des routes authentifiées mais j'ai découvert que React Router 4 empêche maintenant cela de fonctionner :

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

Avertissement : Vous ne devez pas utiliser <Route component> y <Route children> dans le même parcours ; <Route children> seront ignorés

Dans ce cas, quelle est la bonne façon de procéder ?

Il apparaît dans 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 d'y parvenir en regroupant un ensemble de routes ?


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
}

Est-il correct d'envoyer une action dans render() ? Ça ne me semble pas correct. Ça ne semble pas vraiment correct avec componentDidMount ou un autre crochet, non plus.

0 votes

Le mieux est de le faire sur componetWillMount si vous n'utilisez pas le rendu côté serveur.

0 votes

@mfahadi, merci pour votre contribution. Je n'utilise pas encore SSR, mais si je veux l'utiliser à l'avenir, dois-je le garder dans le rendu ? De plus, si l'utilisateur est redirigé dans componentWillMount Pourront-ils jamais voir le résultat rendu, même une fraction de seconde ?

0 votes

Je suis vraiment désolé de dire ça componentWillMount() n'est pas appelé sur SSR, il est componentDidMount() qui n'est pas appelé. comme componentWillMount() est appelé avant render() L'utilisateur ne verra donc pas de nouveau composant. C'est donc le meilleur endroit pour vérifier.

326voto

Tyler McGinnis Points 3675

Vous allez vouloir utiliser le Redirect composant. Il existe plusieurs approches différentes de ce problème. En voici une que j'aime bien : il s'agit d'un composant PrivateRoute qui prend en charge un fichier authed puis effectue le rendu en fonction de cette proposition.

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 Route peuvent ressembler à ceci

<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 n'y comprenez toujours rien, j'ai écrit ce billet qui peut vous aider Routes protégées et authentification avec React Router v4

2 votes

Oh, c'est similaire à ma solution, mais elle utilise <Redirect /> . Le problème est le suivant <Redirect /> ne semble pas fonctionner avec redux dans mon cas ? J'ai besoin de distribuer une action

4 votes

Je ne sais pas pourquoi, mais ajouter state: {from: props.location}}} a provoqué un maximum call stack exceeded . J'ai dû le supprimer. Pourriez-vous expliquer pourquoi cette option est utile @Tyler McGinnis ?

0 votes

@KeitIG C'est étrange. C'est utile car cela vous indique d'où vous venez. Par exemple, si vous voulez que l'utilisateur s'authentifie et qu'une fois qu'il l'a fait, vous le ramenez à la page à laquelle il essayait d'accéder avant de le rediriger.

18voto

MrDuDuDu Points 16

Tnx Tyler McGinnis pour la solution. Je me suis inspiré de l'idée de Tyler McGinnis.

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

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

Vous pouvez le mettre en œuvre comme suit

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

decisionFunc juste une fonction qui retourne vrai ou faux

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

0 votes

Est-ce que cela fonctionne avec un DecisionFunc asynchrone tel que await Api.getLogedStatus() ?

12voto

Hemanthvrm Points 335

(Utilisation de Redux pour la gestion de l'état)

Si l'utilisateur essaie d'accéder à une url, je vais d'abord vérifier si le jeton d'accès est disponible, sinon je redirige vers la page de connexion, Une fois que l'utilisateur se connecte en utilisant la page de connexion, nous stockons cela dans le localstorage ainsi que dans notre état redux. (localstorage ou cookies..nous gardons ce sujet hors du contexte pour le moment).
puisque l'état de redux a été mis à jour et que les routes privées seront redirigées. maintenant nous avons un jeton d'accès et nous allons rediriger vers la page d'accueil.

Stockez les données décodées de la charge utile de l'autorisation également dans l'état redux et passez-les au contexte react. (Nous n'avons pas besoin d'utiliser le contexte mais pour accéder à l'autorisation dans n'importe lequel de nos composants enfants imbriqués, il est plus facile d'y accéder à partir du contexte plutôt que de connecter chaque composant enfant à redux)

Toutes les routes qui n'ont pas besoin de rôles spéciaux peuvent être accédées directement après la connexion S'il a besoin d'un rôle comme admin (nous avons fait une route protégée qui vérifie s'il a le rôle désiré, sinon il est redirigé vers un composant non autorisé).

de la même manière dans n'importe quel composant si vous devez désactiver un bouton ou quelque chose en fonction du rôle.

simplement vous pouvez faire de cette manière

const authorization = useContext(AuthContext);
const [hasAdminRole] = checkAuth({authorization, roleType:"admin"});
const [hasLeadRole] = checkAuth({authorization, roleType:"lead"});
<Button disable={!hasAdminRole} />Admin can access</Button>
<Button disable={!hasLeadRole || !hasAdminRole} />admin or lead can access</Button>

Et si l'utilisateur essaie d'insérer un jeton factice dans le stockage local ? Comme nous avons un jeton d'accès, nous allons rediriger vers le composant d'accueil. Mon composant d'accueil fera un appel au repos pour récupérer les données, puisque le jeton jwt était factice, l'appel au repos retournera un utilisateur non autorisé. Je fais donc un appel à la déconnexion (qui effacera le stockage local et redirigera à nouveau vers la page de connexion). Si la page d'accueil contient des données statiques et ne fait pas d'appel api (alors vous devriez avoir un appel api de vérification de jeton dans le backend afin de pouvoir vérifier si le jeton est RÉEL avant de charger la page d'accueil).

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Switch } from 'react-router-dom';
import history from './utils/history';

import Store from './statemanagement/store/configureStore';
import Privateroutes from './Privateroutes';
import Logout from './components/auth/Logout';

ReactDOM.render(
  <Store>
    <Router history={history}>
      <Switch>
        <Route path="/logout" exact component={Logout} />
        <Route path="/" exact component={Privateroutes} />
        <Route path="/:someParam" component={Privateroutes} />
      </Switch>
    </Router>
  </Store>,
  document.querySelector('#root')
);

Histoire.js

import { createBrowserHistory as history } from 'history';

export default history({});

Privateroutes.js

import React, { Fragment, useContext } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { AuthContext, checkAuth } from './checkAuth';
import App from './components/App';
import Home from './components/home';
import Admin from './components/admin';
import Login from './components/auth/Login';
import Unauthorized from './components/Unauthorized ';
import Notfound from './components/404';

const ProtectedRoute = ({ component: Component, roleType, ...rest })=> { 
const authorization = useContext(AuthContext);
const [hasRequiredRole] = checkAuth({authorization, roleType});
return (
<Route
  {...rest}
  render={props => hasRequiredRole ? 
  <Component {...props} /> :
   <Unauthorized {...props} />  } 
/>)}; 

const Privateroutes = props => {
  const { accessToken, authorization } = props.authData;
  if (accessToken) {
    return (
      <Fragment>
       <AuthContext.Provider value={authorization}>
        <App>
          <Switch>
            <Route exact path="/" component={Home} />
            <Route path="/login" render={() => <Redirect to="/" />} />
            <Route exact path="/home" component={Home} />
            <ProtectedRoute
            exact
            path="/admin"
            component={Admin}
            roleType="admin"
          />
            <Route path="/404" component={Notfound} />
            <Route path="*" render={() => <Redirect to="/404" />} />
          </Switch>
        </App>
        </AuthContext.Provider>
      </Fragment>
    );
  } else {
    return (
      <Fragment>
        <Route exact path="/login" component={Login} />
        <Route exact path="*" render={() => <Redirect to="/login" />} />
      </Fragment>
    );
  }
};

// my user reducer sample
// const accessToken = localStorage.getItem('token')
//   ? JSON.parse(localStorage.getItem('token')).accessToken
//   : false;

// const initialState = {
//   accessToken: accessToken ? accessToken : null,
//   authorization: accessToken
//     ? jwtDecode(JSON.parse(localStorage.getItem('token')).accessToken)
//         .authorization
//     : null
// };

// export default function(state = initialState, action) {
// switch (action.type) {
// case actionTypes.FETCH_LOGIN_SUCCESS:
//   let token = {
//                  accessToken: action.payload.token
//               };
//   localStorage.setItem('token', JSON.stringify(token))
//   return {
//     ...state,
//     accessToken: action.payload.token,
//     authorization: jwtDecode(action.payload.token).authorization
//   };
//    default:
//         return state;
//    }
//    }

const mapStateToProps = state => {
  const { authData } = state.user;
  return {
    authData: authData
  };
};

export default connect(mapStateToProps)(Privateroutes);

checkAuth.js

import React from 'react';

export const AuthContext = React.createContext();

export const checkAuth = ({ authorization, roleType }) => {
  let hasRequiredRole = false;

  if (authorization.roles ) {
    let roles = authorization.roles.map(item =>
      item.toLowerCase()
    );

    hasRequiredRole = roles.includes(roleType);
  }

  return [hasRequiredRole];
};

ÉCHANTILLON DE JETON JWT DÉCODÉ

{
  "authorization": {
    "roles": [
      "admin",
      "operator"
    ]
  },
  "exp": 1591733170,
  "user_id": 1,
  "orig_iat": 1591646770,
  "email": "hemanthvrm@stackoverflow",
  "username": "hemanthvrm"
}

0 votes

Et comment gérez-vous l'accès direct à Signin ? Si un utilisateur sait qu'il n'est pas connecté, il devrait avoir une option pour accéder directement à Signin, non ?

0 votes

@carkod...Par défaut, s'il essaie d'accéder à n'importe quelle route, il sera redirigé vers la page de connexion...(puisqu'il n'aura pas de jeton).

0 votes

@carkod... une fois que l'utilisateur a cliqué sur logout ou que mon jeton de rafraîchissement jwt a expiré... j'appelle la fonction logout où j'efface le localstorage et rafraîchis la fenêtre... donc le localstorage n'aura pas de jeton... il redirigera automatiquement vers la page de login

4voto

Jose G Varanam Points 84

Installer react-router-dom

puis créer deux composants, l'un pour les utilisateurs valides et l'autre pour les utilisateurs invalides.

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;

6 votes

Par itinéraire ? Ça ne marchera pas.

2voto

Gonzalo Cañada Points 66

Je sais que ça fait un moment mais j'ai travaillé sur un Paquet NPM pour les voies privées et publiques.

Voici comment créer 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 routes publiques auxquelles 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 cela vous aidera !

0 votes

Pouvez-vous fournir plus d'exemples incluant tous les imports et wraps, par exemple dans 2 publicroutes, 2 private routes et 2 PropsRoute, dans l'App.js principal ? merci.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X