web-dev-qa-db-ja.com

反応アプリでトークンが期限切れになったときにユーザーをログアウトする方法

私はReactをフロントエンドとして使用し、API呼び出しにReact-apollo-graphqlを使用しているアプリで作業しています。

私はreact-hooksを使用しています。つまり、React 16.8 +。

私がやっていること

auth.jsファイルを作成しました。ユーザーがログインし、トークンが有効であるかどうかをチェックしているときに値を保存しています(有効期限が切れていることを確認しています)が、そのファイルがロードしているのは、更新中またはページの再読み込み、それはそれが機能するはずの方法ではありません

My 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を使用するときにストアファイルで行うように、各ルートではなくページの再読み込みで機能する理由です。

My App.js

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

My 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のみを使用しています。

私の唯一の質問は私がそれを正しくやっているのですか?私は他のアプローチを受け入れます。

Vue Vuexを使用して認証と承認を行いました。これまでのルートで実行されるストアファイルを作成するために使用しています。

私が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 timeを確認)無効な場合、ログアウトしてローカルストレージをクリアします。ストレージのクリアは大したことではありません。ログインページにリダイレクトする方法です。

4
manish thakur

あなたが直面している問題は簡単です。 AuthReducerは、作成時にinitialStateを1回だけ受け取ります。アプリをリロードすると、すべてが再び初期化され、有効期限はロジックによって処理されます。ただし、ルート変更時は、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: {
      ...
    }
  }
})
1
Shubham Khatri

あなたの問題の解決策は次のようになります:

  • コンテキストから認証部分を削除します。 (悪い練習)
  • 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>;

0
Yogesh Aggarwal