web-dev-qa-db-ja.com

React-ログインと認証を処理する最良の方法は何ですか?

認証/ログインを使用してアプリケーションに反応し、動作するようになりました。現在は動作していますが、一緒にハッキングされているように感じます。現在、routes.jsにあるisAuthenticated状態は次のようになっています。

class Routes extends Component {

    constructor(props) {
        super(props);

        this.state = {
            isAuthenticated: false,
         }
     }

ログインページで、ユーザーがhomeページにリダイレクトするために認証されるタイミングを知る必要があります。このisAuthenticated状態へのアクセスと操作を可能にする最適な設計パターンは何ですか?現在どのように設定しているのかは、routes.js内の状態を設定し、そのような状態を小道具として送信する関数があることです。

 setAuthenticated = (isAuthenticated) => {
        this.setState({isAuthenticated});
    }

下のルーターで...

<Route path="/" exact component={() =>
                            <div>
                                <Login
                                    isAuthenticated={this.state.isAuthenticated}
                                    setAuthenticated={this.setAuthenticated}
                            </div>
                        } />

はい、不変であると思われる小道具の値を変更しているため、これは悪い設計であることを理解しています。 login.jsでこの値を変更すると、複数の不必要な再レンダリングが発生するため、これも悪いことです。 isAuthenticatedを何らかのグローバル変数として宣言する必要がありますか?ちなみに、私は状態管理を使用していません。

編集:正しいログイン/パスワードの組み合わせを確認するサーバーからの応答に基づいてisAuthenticatedを設定しています。

12
Vincent Nguyen

isAuthenticatedstateのみで処理すると、ユーザーはページを更新するたびに認証されなくなります。それは本当にユーザーフレンドリーではありません! :)

そのため、代わりに、ログインページにaccess_token(バックエンドから来る)cookies または localStorage のブラウザaccess_tokenは、ユーザーが認証されていることを証明し、ユーザーの身元も検証します。通常、これはaccess_tokenサーバーへの次のすべての要求に対して、このユーザーが要求しているデータへのアクセスを許可されているか、または作成、編集、削除しようとしているものの作成、編集、削除を許可されているかを確認します。

その後、これを確認できますaccess_token他のすべてのページでも同様であり、ユーザーがもう認証されていない場合は、ログインページにリダイレクトします。


access_tokenおよびrefresh_token以下のコードを理解するのに役立ちますが、すでに慣れている場合はお気軽にスキップしてください

バックエンドはおそらく OAuth2 、これは現在最も一般的な認証プロトコルです。 OAuth2、アプリは認証するユーザーのユーザー名とパスワードを含む最初のリクエストをサーバーに送信します。ユーザーが認証されると、1)access_token(通常は1時間後に期限切れになります)、および2)refresh_tokenは、非常に長い時間(時間、日)後に期限切れになります。 access_tokenが期限切れになり、ユーザーにユーザー名とパスワードを再度要求する代わりに、アプリはrefresh_tokenサーバーに新しいaccess_tokenこのユーザー。


cookieslocalStorageの違いに関する簡単な説明– feelスキップしても構いません!

localStorageは両方の最新のテクノロジーです。これは、単純なキー/値永続化システムであり、access_tokenとその値。ただし、有効期限も保持する必要があります。 expiresのような2番目のキー/値情報を保存することもできますが、処理するロジックが増えます。

一方、cookiesにはネイティブのexpiresプロパティが付属しています。これはまさに必要なものです。 cookiesは古いテクノロジーであり、開発者にとって使い勝手が悪いため、個人的に js-cookie 、これはcookiesを操作するための小さなライブラリです。

cookiesのその他の利点:クロス サブドメイン !ログインアプリがlogin.mycompany.comおよびメインアプリはapp.mycompany.com、ログインアプリでcookieを作成し、メインアプリからアクセスできます。これはLocalStorageでは不可能です。


以下は、認証に使用するいくつかのメソッドと特別なReactコンポーネントです。

isAuthenticated()

import Cookies from 'js-cookie'

export const getAccessToken = () => Cookies.get('access_token')
export const getRefreshToken = () => Cookies.get('refresh_token')
export const isAuthenticated = () => !!getAccessToken()

認証する()

export const authenticate = async () => {
  if (getRefreshToken()) {
    try {
      const tokens = await refreshTokens() // call an API, returns tokens

      const expires = (tokens.expires_in || 60 * 60) * 1000
      const inOneHour = new Date(new Date().getTime() + expires)

      // you will have the exact same setters in your Login page/app too
      Cookies.set('access_token', tokens.access_token, { expires: inOneHour })
      Cookies.set('refresh_token', tokens.refresh_token)

      return true
    } catch (error) {
      redirectToLogin()
      return false
    }
  }

  redirectToLogin()
  return false
}

redirectToLogin()

const redirectToLogin = () => {
  window.location.replace(
    `${getConfig().LOGIN_URL}?next=${window.location.href}`
  )
  // or history.Push('/login') if your Login page is inside the same app
}

AuthenticatedRoute

export const AuthenticatedRoute = ({
  component: Component,
  exact,
  path,
}) => (
  <Route
    exact={exact}
    path={path}
    render={props =>
      isAuthenticated() ? (
        <Component {...props} />
      ) : (
        <AuthenticateBeforeRender render={() => <Component {...props} />} />
      )
    }
  />
)

AuthenticateBeforeRender

class AuthenticateBeforeRender extends Component {
  state = {
    isAuthenticated: false,
  }

  componentDidMount() {
    authenticate().then(isAuthenticated => {
      this.setState({ isAuthenticated })
    })
  }

  render() {
    return this.state.isAuthenticated ? this.props.render() : null
  }
}
32
GG.

ページ保護が「isAuthenticated」状態変数に依存している場合、おそらく react devtoolsを無効にする を本番環境で使用する必要があります。それ以外の場合、ページを検査し、フラグを手動でtrueに切り替えて、保護されたページを認証されていないユーザーに公開することができます。

4
bje

ログイン時にローカルストレージにアクセストークンを設定し、ユーザーがログアウトした後にクリアすることができます。次に、認証済みメソッドを使用して、トークンがあるかどうか、およびAPI呼び出しの実行中にトークンが有効かどうかを確認します

2
Waweru Mwaura

1つのセッションの間のみ認証が続くアプリケーションを使用している場合、状態に保存するだけで十分です。ただし、これは、ユーザーがページの更新時に認証済みステータスを失うことを意味することに注意してください。

以下にReact Contextを使用した例を示します。ここでは、createContextを使用してコンテキストを作成し、Consumerを使用してアプリケーション全体にアクセスします。

const AuthenticationContext = React.createContext();
const { Provider, Consumer } = AuthenticationContext;

function Login(props) {
  return (
    <Consumer>
      {
        value=>
        <button onClick={value.login}>Login</button>
      }
    </Consumer>
  );
}

function Logout() {
  return (
    <Consumer>
      {
        value=>
        <button onClick={value.logout}>Logout</button>
      }
    </Consumer>
  );
}

function AnotherComponent() {
  return (
    <Consumer>
      {
        value=>{
          return value.isAuthenticated?
            <p>Logged in</p>:
            <p>Not Logged in</p>
        }
      }
    </Consumer>
  );
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.login = ()=> {
      this.setState({
        isAuthenticated: true
      });
    }
    this.logout = ()=> {
      this.setState({
        isAuthenticated: false
      });
    }
    this.state = {
      isAuthenticated: false,
      login: this.login,
      logout: this.logout
    }
  }
  
  render() {
    return (
      <Provider value={this.state}>
        <Login />
        <Logout />
        <AnotherComponent />
      </Provider>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div> 

https://reactjs.org/docs/context.html#reactcreatecontext