web-dev-qa-db-fra.com

Sur React Routeur, comment rester connecté à l’état même actualiser la page?

Je crée le site avec React et React Routeur avec Redux. De nombreux itinéraires (pages) doivent être connectés. Je peux me rediriger si l'utilisateur est non 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é onEnter hook pour rediriger vers la route '/ login' si l'utilisateur n'est pas connecté. Les données permettant de vérifier l'utilisateur connecté sont dans le magasin et seront mises à jour après la connexion de l'utilisateur.

Cela fonctionne parfaitement, mais le problème est que lorsque j'actualise la page, le magasin est réinitialisé et l'utilisateur n'est pas connecté à l'état précédent.

Je sais que cela se produit car le magasin Redux est uniquement un stockage de mémoire, donc la page de refesh perdra toutes les données.

Vérifier la session du serveur à chaque actualisation peut être un travail mais cela peut être trop de demande, donc ça a l'air mauvaise idée.

Enregistrer les données d'état connectées dans localStorage peut être un travail, mais dans ce cas, je devrais vérifier chaque AJAX échouent parce que la demande est rejetée car la session a expiré ou n'existe pas, ce qui ressemble à quelque chose, mauvaise idée non plus.

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

Tout conseil sera très apprécié.

36
modernator

Une autre solution consiste à utiliser jetons Web JSON (JWT) requis pour chaque route et localStorage pour rechercher le JWT.

TL; DR

  • Sur le serveur frontal, vous avez un itinéraire de connexion et d'inscription qui interroge votre serveur pour un JWT en fonction de l'authentification sur le serveur. Une fois passé le fichier JWT approprié, vous définissez une propriété de l'état sur true. Vous pouvez avoir un itinéraire de sortie permettant à l'utilisateur de définir cet état sur false.

  • Le fichier index.js qui contient vos itinéraires peut vérifier le stockage local avant le rendu, éliminant ainsi le problème de perte d'état lors de l'actualisation, tout en préservant une certaine sécurité.

  • Tous les itinéraires nécessitant une authentification dans votre application sont rendus par le biais d'un composant composé et sont sécurisés avec la nécessité de disposer de JWT dans l'en-tête pour une autorisation sur l'API du serveur.

Cela prend un peu de temps, mais votre application sera "raisonnablement" sécurisée.


Pour résoudre votre problème:

Vérifiez le stockage local avant les routes dans votre index.js fichier comme indiqué ci-dessous, mise à jour de l'état à authentifié si nécessaire.

L'application maintient la sécurité avec le fait que l'API est sécurisée par le JWT, ce qui résoudrait votre problème d'actualisation et maintiendrait 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é alors que Graph et IsAuthenticated (peuvent être un nombre quelconque de composants nommés de manière appropriée) nécessitent le state.authenticated pour être vrai.

Les composants, dans ce cas Graph et IsAuthenticated restitués si le state.authenticated est vrai. Sinon, les valeurs par défaut reviennent à la route racine.


Vous pouvez ensuite créer un composant composé comme celui-ci, à travers lequel tous vos itinéraires sont rendus. Il vérifiera que l'état dans lequel vous vous trouvez, que l'utilisateur soit authentifié ou non (un booléen), est vrai avant 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/de la connexion, vous pouvez créer une action qui stocke le JWT et configure l'état pour qu'il soit authentifié via un créateur de l'action -> magasin de redux. Cet exemple tilise axios pour exécuter le cycle de réponse à une demande HTTP asynchrone.

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 devez également configurer votre magasin (Redux dans ce cas) et votre créateur d'action bien sûr.

La "vraie" sécurité vient de l'arrière. Et pour ce faire, vous utilisez localStorage afin de conserver le JWT sur le serveur frontal et de le transmettre dans l'en-tête à tous les appels d'API contenant des informations sensibles/protégées.

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

38
alexi2

Pourquoi ne pas utiliser sessionStorage avec l'état et la date d'expiration connectés? Vous devrez écrire plus de code pour vérifier l’état sessionStorage, mais c’est le seul moyen, à mon avis, d’éviter que l’appel XHR soit envoyé.

1
aviramtsi