web-dev-qa-db-fra.com

Comment déconnecter l'utilisateur lorsque le jeton expire dans l'application React

Je travaille sur une application où j'utilise React comme front-end et React-apollo-graphql pour mon appel API.

J'utilise react-hooks c'est-à-dire dans React 16.8 +.

Ce que je fais

J'ai créé un auth.js fichier dans lequel je stocke mes valeurs lorsque l'utilisateur se connecte et vérifie également que le jeton est valide ou non, (expiration que je vérifie), mais ce fichier ne charge que mon Je rafraîchis ou recharge la page, ce n'est pas comment ça devrait fonctionner

Mon fichier auth.js

const initialstate = {
    user: null,
};
if (localStorage.getItem("JWT_Token")) {
    const jwt_Token_decoded = Jwt_Decode(localStorage.getItem("JWT_Token"));
    console.log(jwt_Token_decoded.exp * 1000);
    console.log(Date.now());
    if (jwt_Token_decoded.exp * 1000 < Date.now()) {
        localStorage.clear(); // this runs only when I refresh the page or reload on route change it dosent work
    } else {
        initialstate.user = jwt_Token_decoded;
    }
}

const AuthContext = createContext({
    user: null,
    login: (userData) => {},
    logout: () => {},
});
const AuthReducer = (state, action) => {
    switch (action.type) {
        case "LOGIN":
        return {
            ...state,
            user: action.payload,
        };
        case "LOGOUT":
        return {
            ...state,
            user: null,
        };
        default:
        return state;
    }
};
    
const AuthProvider = (props) => {
    const [state, dispatch] = useReducer(AuthReducer, initialstate);
    const login = (userData) => {
        localStorage.setItem("JWT_Token", userData.token);
        dispatch({
        type: "LOGIN",
        payload: userData,
        });
    };
    const logout = () => {
        localStorage.clear();
        dispatch({ action: "LOGOUT" });
    };
    
    return (
        <AuthContext.Provider
        value={{ user: state.user, login, logout }}
        {...props}
        />
    );
};
    
export { AuthContext, AuthProvider };

Comme je l'ai commenté, la ligne où je vérifie l'expiration du jeton.

Mon seul problème est de savoir pourquoi cela fonctionne sur le rechargement de la page et non sur chaque route comme nous le faisons dans le fichier de stockage lorsque nous utilisons Redux.

Mon App.js

<AuthProvider>
  <Router>
    <div className="App wrapper">
      <Routes/>
    </div>
  </Router>
</AuthProvider>

Mon index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import ApolloClient from 'apollo-boost'
import { ApolloProvider } from '@apollo/react-hooks';
import { InMemoryCache } from 'apollo-cache-inmemory';
    
const client = new ApolloClient({
  uri: 'my url',
  cache: new InMemoryCache(),
});
ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);

Points importants

Comme j'utilise react-apollo-graphql alors fournissent-ils un flux d'authentification ant? comme le fait redux, nous devons créer un fichier de stockage qui stockera nos données

J'utilise React 16.8 + donc j'utilise des react-hooks donc ici j'utilise use Reducer à partir de cela seulement.

Ma seule question est est-ce que je le fais correctement? Je suis ouvert à d'autres approches.

J'ai fait l'authentification et l'autorisation dans Vue en utilisant Vuex là-bas, j'utilise pour créer un fichier de magasin qui s'exécute sur chaque route

Même chose que j'ai fait avec Redux, dans mon fichier de magasin, j'utilise pour stocker les états et tout.

Maintenant, si j'utilise react-hooks et react-apollo-graphql donc pas besoin de faire cela avec redux.

J'utilise apollo-link-context pour passer l'en-tête (Authorization) comme ci-dessous

const authLink = setContext(() => {
  const token = localStorage.getItem('JWT_Token')
  return {
    headers:{
      Authorization: token ? `${token}` : ''
    }
  }
});

Je pense qu'ici je peux vérifier sur chaque route ou sur chaque demande si le jeton est valide ou non (vérifier le temps d'expiration) s'il n'est pas valide, je vais me déconnecter et effacer mon stockage local, Effacer le stockage n'est pas un gros problème l'essentiel est comment rediriger vers la page de connexion.

4
manish thakur

Le problème auquel vous faites face est simple. Votre AuthReducer n'accepte initialState qu'une seule fois lors de sa création. Désormais, lorsque vous rechargez votre application, tout est à nouveau initialisé et l'expiration est prise en charge par votre logique. Cependant, lors du changement de route, il ne réévalue pas votre état initial.

Cependant, ce que vous pouvez faire, c'est en utilisant setContext, vous pouvez vérifier la validation de l'expiration en décodant le jeton en utilisant jwtDecode et actualiser le jeton s'il a expiré et enregistrer dans localStorage puisque ceci est exécuté à chaque demande

const authLink = setContext(async () => {
  let token = localStorage.getItem('JWT_Token')
  const { exp } = jwtDecode(token)
  // Refresh the token a minute early to avoid latency issues
  const expirationTime = (exp * 1000) - 60000
  if (Date.now() >= expirationTime) {
    token = await refreshToken()
    // set LocalStorage here based on response;
  }
  return {
    // you can set your headers directly here based on the new token/old token
    headers: {
      ...
    }
  }
})

Cependant, puisque vous souhaitez rediriger vers la page de connexion et ne pas actualiser le jeton lorsque le jeton a expiré, vous pouvez utiliser un objet d'historique personnalisé avec Routes

src/history.js

import { createBrowserHistory } from 'history';
const history = createBrowserHistory()
export default history;

App.js

import history from '/path/to/history.js';
import { Router } from 'react-router-dom';

<AuthProvider>
  <Router history={history}>
    <div className="App wrapper">
      <Routes/>
    </div>
  </Router>
</AuthProvider>

puis dans setContext vous pourriez faire

import history from '/path/to/history';
const authLink = setContext(async () => {
  let token = localStorage.getItem('JWT_Token')
  const { exp } = jwtDecode(token)
  const expirationTime = (exp * 1000) - 60000
  if (Date.now() >= expirationTime) {
    localStorage.clear();
    history.Push('/login');
  }
  return {
    // you can set your headers directly here based on the old token
    headers: {
      ...
    }
  }
})
1
Shubham Khatri

Pour votre problème, la solution pourrait être comme:

  • Supprimez la partie auth du contexte. (Mauvaise pratique)
  • Créez un composant avec react-router s'est abonné pour vérifier l'état d'authentification de l'utilisateur.
  • Rendez-le dans le composant main.

authverify.component.js

import { withRouter } from "react-router-dom";

const AuthVerifyComponent = ({ history }) => {
  history.listen(() => {  // <--- Here you subscribe to the route change
    if (localStorage.getItem("JWT_Token")) {
      const jwt_Token_decoded = Jwt_Decode(localStorage.getItem("JWT_Token"));
      console.log(jwt_Token_decoded.exp * 1000);
      console.log(Date.now());
      if (jwt_Token_decoded.exp * 1000 < Date.now()) {
        localStorage.clear();
      } else {
        initialstate.user = jwt_Token_decoded;
      }
    }
  });
  return <div></div>;
};

export default withRouter(AuthVerifyComponent);

app.js

<AuthProvider>
  <Router>
    <div className="App wrapper">
      <Routes />
      <AuthVerifyComponent />
    </div>
  </Router>
</AuthProvider>;

0
Yogesh Aggarwal