【问题标题】:How to logout user when token expires in react appreact app中令牌过期时如何注销用户
【发布时间】:2020-09-11 19:05:53
【问题描述】:

我正在开发一个应用程序,我使用 React 作为我的前端,React-apollo-graphql 用于我的 API 调用。

我正在使用react-hooks,即在 React 16.8 + 中。

我在做什么

我已经创建了一个 auth.js 文件,当用户登录时我在其中存储我的值并检查令牌是否有效(我正在检查到期),但该文件仅加载我正在刷新或重新加载页面,这不是应该的工作方式

我的 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 };

正如我已经评论了我正在检查令牌到期的行。

我唯一的问题是为什么它在页面重新加载上工作,而不是像我们在使用 Redux 时在存储文件中所做的那样在每个路由上工作。

我的 App.js

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

我的 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')
);

要点

当我使用react-apollo-graphql 时,他们是否提供 ant 身份验证流程?就像 redux 一样,我们必须创建一个存储文件来存储我们的数据

我使用的是 React 16.8 +,所以我使用的是 react-hooks,所以这里我只使用 use Reducer

我唯一的问题是我做得对吗?我对其他方法持开放态度。

我已经使用 Vuex 在 Vue 中完成了身份验证和授权,我用它来创建一个在任何路线上运行的商店文件

与 Redux 一样,在我的存储文件中我用来存储状态和所有内容。

现在,如果我使用 react-hooks 和 react-apollo-graphql,那么就不需要使用 redux 来做这些事情了。

我正在使用apollo-link-context 传递如下所示的标头(授权)

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

我认为在这里我可以检查每个路由或每个请求是否令牌有效(检查 exp 时间)如果它无效然后我将注销并清除我的本地存储,清除存储不是什么大问题主要是如何重定向到登录页面。

【问题讨论】:

    标签: javascript reactjs graphql react-hooks react-apollo


    【解决方案1】:

    您面临的问题很简单。您的 AuthReducer 在创建时只接受一次 initialState。现在,当您重新加载您的应用程序时,一切都会再次初始化,并且到期由您的逻辑处理。但是在路线更改时,它不会重新评估您的初始状态。

    但是,您可以在使用 setContext 时通过使用 jwtDecode 解码令牌来检查是否过期,如果令牌过期则刷新令牌并保存在 localStorage 中,因为这是在每次请求时执行的

    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: {
          ...
        }
      }
    })
    

    但是,由于您希望在令牌过期时重定向到登录页面而不刷新令牌,您可以使用带有路由的自定义历史对象

    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>
    

    然后在 setContext 你可以做

    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: {
          ...
        }
      }
    })
    

    【讨论】:

    • 我没有得到这个 refreshToken() 我没有创建这个函数,我已经尝试过这种方法,比如通过检查jwtDecode 导出令牌时我想注销,即重定向到登录页面并清除令牌,但这里我不知道如何使用重定向或路由。
    • 好的,更新我的答案
    • '/path/to/history' 我应该放什么路径?
    • history.js 文件的相对路径
    • 我还没有创建历史文件,我正在为此使用 react useHistory Hook。
    【解决方案2】:

    对于您的问题,解决方案可能如下:

    • 从上下文中删除身份验证部分。 (不好的做法)
    • 创建一个订阅了react-router的组件来检查用户的身份验证状态。
    • 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>;
    
    

    【讨论】:

    • 我已经有一个单独的路由文件,其中写入了所有路由,并且会在每次路由检查时运行,我的意思是在每次路由渲染时运行?
    • 是的,你可以试试。我以前做过,但现在没试过。
    • 嘿,基本上你是说删除 auth.js 文件并将我所有的代码放在你创建的新组件中 AuthVerifyComponent
    • 不,只需将验证部分移至AuthVerifyComponent,以便您可以使用withRouter 钩子,其余部分保持不变。
    • 所以它说初始状态未定义,我也必须通过初始状态,如何?
    【解决方案3】:

    从 Yogesh Aggarwal 的回答中得到启发,我更喜欢这样做:

    
    import React from 'react';
    import {useLocation, useHistory} from 'react-router-dom';
    
    const AuthProvider = () => {
      const pathName = useLocation().pathname;
      const history = useHistory();
    
      if (pathName === your_path_that_need_authentication) {
            // if token expired  then   history.push(login_page));
      }
    
      return null;
    };
    
    export default AuthProvider;
    
    

    然后把这个 AuthProvider 放到项目中。

    从历史中提取路径名是一个很好的选择,但不是最好的选择。问题是如果您键入或复制/粘贴所需的 URL,它不再起作用,但使用 useLocation 挂钩将解决此问题

    【讨论】:

      猜你喜欢
      • 2018-07-30
      • 1970-01-01
      • 2017-12-12
      • 2021-07-31
      • 2020-02-20
      • 2020-04-16
      • 2022-01-23
      • 2022-01-25
      • 2021-03-07
      相关资源
      最近更新 更多