58 votes

Sur React Router, comment garder l'état de connexion même en rafraîchissant la page ?

Je réalise le site avec React et React Router avec Redux. Beaucoup de routes (pages) nécessitent une connexion. Je peux rediriger vers le login si l'utilisateur n'est pas connecté comme ceci :

function requireAuth(nextState, replace) {
    let loggedIn = store.getState().AppReducer.UserReducer.loggedIn;

    if(!loggedIn) {
        replace({
            pathname: '/login',
            state: {
                nextpathname: nextState.location.pathname
            }
        });
    }
}

ReactDOM.render(
    <Provider store={store}>
        <Router history={history}>
            <Route path="/" component={App}>
                <IndexRoute component={Index} />
                <Route path="login" component={Login} />
                <Route path="register" component={Register} />
                <Route path="dashboard" component={Graph} onEnter={requireAuth}>
                    ... some other route requires logged in ...
                </Route>
            </Route>
        </Router>
    </Provider>,
    document.getElementById('entry')
);

Veuillez voir le code, j'ai utilisé le hook onEnter pour rediriger vers la route '/login' si l'utilisateur n'est pas connecté. Les données pour vérifier que l'utilisateur est connecté sont dans le magasin, et elles seront mises à jour après que l'utilisateur se soit connecté.

Cela fonctionne parfaitement, mais le problème est que lorsque je rafraîchis la page, le magasin est réinitialisé et l'utilisateur n'est pas connecté à nouveau.

Je sais que cela se produit parce que le magasin Redux est juste un stockage en mémoire, donc la page de refonte perdra toutes les données.

Vérifier la session du serveur à chaque rafraîchissement peut fonctionner, mais cela peut représenter une demande trop importante, ce qui est une mauvaise idée.

Sauvegarder les données de l'état de connexion dans le localStorage pourrait fonctionner, mais dans ce cas, je devrais vérifier à chaque appel AJAX que la demande est rejetée parce que la session a expiré ou n'existe pas, et cela semble être une mauvaise idée.

Existe-t-il un moyen de résoudre ce problème plus clairement ? Mon site web va utiliser beaucoup de personnes, donc je veux réduire les appels XHR autant que possible.

Tout conseil sera très apprécié.

68voto

alexi2 Points 3048

Une autre façon de procéder est d'utiliser Jetons Web JSON (JWT) qui sont nécessaires pour chaque route, et localStorage pour vérifier la présence du JWT.

TL;DR

  • Sur le front, vous avez une route d'identification et d'inscription qui interroge votre site Web. serveur pour un JWT selon l'authentification sur le serveur. Une fois que passé le JWT approprié, vous pouvez alors définir une propriété de state à true. Vous pouvez avoir une route de sortie qui permet à l'utilisateur de définir cet état à false. état à false.

  • L'index.js qui contient vos routes peut vérifier le stockage local avant d'effectuer le rendu, éliminant ainsi votre problème de perte d'état lors du rafraîchissement mais en gardant une certaine sécurité.

  • Toutes les routes nécessitant une authentification dans votre application sont rendues par le biais d'un Composant Composé, et sécurisées avec la nécessité de d'avoir des JWTs dans l'en-tête pour l'autorisation sur l'API du serveur.

Cette configuration prend un peu de temps, mais elle rendra votre application "raisonnablement" sûre.


Pour résoudre votre problème :

Vérifiez le stockage local avant les itinéraires dans votre index.js comme indiqué ci-dessous, en mettant à jour l'état à authentifié si nécessaire.

L'application maintient la sécurité grâce au fait que l'API est sécurisée par le JWT, ce qui résout votre problème de rafraîchissement et maintient un lien sécurisé avec votre serveur et vos données.

Ainsi, dans les itinéraires, vous auriez quelque chose comme ceci :

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import { Router, Route, browserHistory, IndexRoute } from 'react-router';
import reduxThunk from 'redux-thunk';
import { AUTHENTICATE_THE_USER } from './actions/types';
import RequireAuth from './components/auth/require_auth';
import reducers from './reducers';

/* ...import necessary components */

const createStoreWithMiddleware = compose(applyMiddleware(reduxThunk))(createStore);

const store = createStoreWithMiddleware(reducers);

/* ... */

// Check for token and update application state if required
const token = localStorage.getItem('token');
if (token) {
    store.dispatch({ type: AUTHENTICATE_THE_USER });
}

/* ... */

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
        <IndexRoute component={Index} />
        <Route path="login" component={Login} />
        <Route path="register" component={Register} />
        <Route path="dashboard" component={RequireAuth{Graph}} />
        <Route path="isauthenticated" component={RequireAuth(IsAuthenticated)} />
        ... some other route requires logged in ...
      </Route>
    </Router>
  </Provider>
  , .getElementById('entry'));

RequiredAuth est le composant composé tandis que Graph et IsAuthenticated (il peut s'agir de n'importe quel nombre de composants nommés de manière appropriée) nécessitent l'utilisation de l'option state.authenticated pour être vrai.

Les composants, dans ce cas Graph et IsAuthenticated rendu si le state.authenticated est vrai. Sinon, il revient par défaut à la route Root.


Vous pourriez alors construire un composant composé comme celui-ci, par lequel toutes vos routes sont rendues. Il vérifiera que l'état dans lequel vous maintenez si l'utilisateur est authentifié ou non (un booléen) est vrai avant d'effectuer le rendu.

require_auth.js

import React, { Component } from 'react';
import { connect } from 'react-redux';

export default function (ComposedComponent) {

  // If user not authenticated render out to root

  class Authentication extends Component {
    static contextTypes = {
      router: React.PropTypes.object
    };

    componentWillMount() {
      if (!this.props.authenticated) {
        this.context.router.push('/');
      }
    }

    componentWillUpdate(nextProps) {
      if (!nextProps.authenticated) {
        this.context.router.push('/');
      }
    }

    render() {
      return <ComposedComponent {...this.props} />;
    }
  }

  function mapStateToProps(state) {
    return { authenticated: state.authenticated };
  }

  return connect(mapStateToProps)(Authentication);
}

Du côté de l'inscription/signature, vous pourriez créer une action qui stocke le JWT et configure l'état pour l'authentification via un créateur d'action -> redux store. Cet exemple fait usage d'axios pour exécuter le cycle asynchrone de réponse aux demandes HTTP.

export function signinUser({ email, password }) {

  // Note using the npm package 'redux-thunk'
  // giving direct access to the dispatch method
  return function (dispatch) {

    // Submit email and password to server
    axios.post(`${API_URL}/signin`, { email, password })
      .then(response => {
        // If request is good update state - user is authenticated
        dispatch({ type: AUTHENTICATE_THE_USER });

        // - Save the JWT in localStorage
        localStorage.setItem('token', response.data.token);

        // - redirect to the route '/isauthenticated'
        browserHistory.push('/isauthenticated');
      })
      .catch(() => {
        // If request is bad show an error to the user
        dispatch(authenticationError('Incorrect email or password!'));
      });
  };
} 

Vous devrez également configurer votre magasin (Redux dans ce cas) et votre créateur d'actions, bien sûr.

La "vraie" sécurité vient de l'arrière. Pour ce faire, vous utilisez localStorage pour conserver le JWT sur le front-end et le transmettre dans l'en-tête de tout appel d'API contenant des informations sensibles/protégées.

La création et l'analyse syntaxique du JWT pour les utilisateurs sur l'API du serveur est une autre étape. J'ai trouvé que le passeport était efficace.

0 votes

Dans votre exemple, IsAuthenticated est le ComposedComponent, correct ?

0 votes

RequiredAuth est le composant composé tandis que IsAuthenticated peut être constitué d'un nombre quelconque de composants nommés de manière appropriée et nécessitant l'utilisation de l'option state.authenticated pour être vrai. Mise à jour de la réponse.

1 votes

Je me débattais avec la configuration de l'état initial dans les méthodes du cycle de vie et cette réponse m'a fait réaliser que je peux mettre de la logique dans le fichier index.js ! !! Merci mon pote.

0voto

aviramtsi Points 51

Pourquoi ne pas utiliser sessionStorage avec l'état de connexion et la date d'expiration ? Vous devrez écrire plus de code pour vérifier l'état de sessionStorage mais c'est, à mon avis, la seule façon d'éviter l'envoi de l'appel XHR.

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