web-dev-qa-db-ja.com

Reactルーター、ページを更新する場合でもログイン状態を維持する方法は?

ReactおよびReact Reduxのルーター。多くのルート(ページ)にログインする必要があります。ユーザーがログインしている場合は、ログインにリダイレクトできます。次のようにログインしていません:

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

コードを参照してください。ユーザーがログインしていない場合、onEnterフックを使用して '/ login'ルートにリダイレクトします。ログインしているユーザーをチェックするためのデータはストアにあり、ユーザーがログインすると更新されます。

完全に機能していますが、問題はページを更新すると、ストアがリセットされ、ユーザーがログイン状態に戻らないことです。

Reduxストアは単なるメモリストレージであるため、refeshページがすべてのデータを失うため、これが起こることを知っています。

更新ごとにサーバーセッションを確認することはできますが、これは要求が多すぎる可能性があるため、良くないようです。

ログインした状態データをlocalStorageに保存すると動作する可能性がありますが、この場合、セッションが期限切れであるか何かのように存在しないため、リクエストが拒否されたすべてのAJAX呼び出しが失敗し、どちらか悪い考え。

この問題をより明確に解決する方法はありますか?私のウェブサイトは多くの人を使用するので、できるだけXHRの呼び出しを減らしたいと思います。

アドバイスをいただければ幸いです。

36
modernator

別の方法としては、各ルートに必要な JSON Web Tokens(JWT) を使用し、 localStorage を使用してJWTを確認します。

TL; DR

  • フロントエンドには、サーバーでの認証に応じてサーバーにJWTを照会するサインインおよびサインアップルートがあります。適切なJWTを渡したら、状態のプロパティをtrueに設定します。ユーザーがこの状態をfalseに設定できるようにするサインアウトルートを持つことができます。

  • ルートを含むindex.jsは、レンダリングの前にローカルストレージをチェックできるため、更新時に状態を失うという問題を解消しますが、セキュリティを維持します。

  • アプリケーションで認証を必要とするすべてのルートは、Composed Componentを介してレンダリングされ、サーバーAPIでの許可のためにヘッダーにJWTを含める必要があることで保護されます。

これを設定するには少し時間がかかりますが、アプリケーションが「合理的に」安全になります。


問題を解決するには:

以下に示すように、index.jsファイルのルートの前にローカルストレージを確認し、必要に応じて状態を認証済みに更新します。

アプリケーションは、更新の問題を解決し、サーバーとデータへの安全なリンクを維持するJWTによってAPIが保護されているという事実により、セキュリティを維持します。

したがって、ルートには次のようなものがあります。

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は構成されたコンポーネントですが、GraphおよびIsAuthenticated(適切な名前のコンポーネントはいくつでもかまいません)はstate.authenticatedがtrueである必要があります。

コンポーネント。この場合、state.authenticatedがtrueの場合にレンダリングされるGraphおよびIsAuthenticated。それ以外の場合、デフォルトはルートルートに戻ります。


次に、このような構成コンポーネントを作成し、すべてのルートをレンダリングします。レンダリングの前に、ユーザーが認証されているかどうか(ブール値)が保持されている状態がtrueであることを確認します。

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);
}

サインアップ/サインイン側で、JWTを保存するアクションを作成し、action-creator-> redux storeを介して状態を認証済みに設定できます。この例 axiosを使用 非同期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!'));
      });
  };
} 

もちろん、ストア(この場合はRedux)とアクションクリエーターをセットアップする必要もあります。

「本当の」セキュリティはバックエンドから来ます。これを行うには、localStorageを使用して、JWTをフロントエンドに保持し、ヘッダーでそれを機密情報または保護された情報を持つAPI呼び出しに渡します。

サーバーAPIでユーザー用のJWTを作成および解析することも、もう1つのステップです。 パスポートが有効であることがわかりました。

38
alexi2

ログイン状態と有効期限でsessionStorageを使用しないのはなぜですか? sessionStorageの状態をチェックするためのコードをさらに書く必要がありますが、それがXHR呼び出しが送信されないようにできる唯一の方法です。

1
aviramtsi