Help us understand the problem. What is going on with this article?

React Routerで認証を制御する方法

More than 3 years have passed since last update.

React Routerで認証を制御する方法です。試行錯誤して良さそうな実装方法を発見したのでご紹介します。

アプリに認証があると、画面ごとに、

  1. 認証済みユーザのみアクセスを許可したいページ
    • アカウント設定など
  2. 認証していないユーザのみアクセスを許可したいページ
    • ログイン・新規登録など
  3. 認証に関係なくアクセスを許可したいページ
    • Qiita投稿のようなパブリックなコンテンツなど

の設定が必要になる場合があります。

React Routerのサンプルをいくつか見たところ幾つか認証を制御する方法が示されていました。

  1. onEnterを使う方法
  2. AuthenticatedComponent

onEnterを使う方法

本家のサンプルで例示された実装です。現在βのreact-router 1.0.0が必要になります。

function requireAuth(nextState, transition) {
  if (!auth.loggedIn())
    transition.to('/login', null, { nextPathname: nextState.location.pathname });
}

React.render((
  <Router history={history}>
    <Route path="/" component={App}>
      <Route path="login" component={Login}/>
      <Route path="logout" component={Logout}/>
      <Route path="about" component={About}/>
      <Route path="dashboard" component={Dashboard} onEnter={requireAuth}/>
    </Route>
  </Router>
), document.getElementById('example'));

僕が試したところ、Flux実装によってはrequireAuth関数とストアをうまくリンクできないという課題がありました。

AuthenticatedComponent

AuthenticatedComponentは認証制御をするコンポーネントを作り、認証が必要なコンポーネントをwrapする実装パターンです。

AutenticatedComponent.jsx
import React from 'react';
import LoginStore from '../stores/LoginStore';

export default (ComposedComponent) => {
  return class AuthenticatedComponent extends React.Component {

    static willTransitionTo(transition) {
      if (!LoginStore.isLoggedIn()) {
        transition.replaceWith('/login');
      }
    }

    ...

    render() {
      return (
      <ComposedComponent {...this.props} />
      );
    }
  }
};

クラスを定義するときに、コンポーネントのクラスをAuthenticatedComponentで囲むようにします。

AccountSetting.jsx
import React from 'react';
import AuthenticatedComponent from './AuthenticatedComponent';

export default AuthenticatedComponent(class AccountSetting extends React.Component {
  render() {
    return <div>...</div>;
  }
});

AuthenticatedComponentはご覧のとおり、認証が必要なコンポーネントすべてに認証コードを記述しないといけないため、新しいページを作った場合、認証コードを記述し忘れるリスクがあります。

このパターンの詳細はAdding authentication to your React Flux appを御覧ください。

認証コンポーネントをルーティングに組み込む方法

以上で見たとおり、onEnterはFlux実装を選ぶし、AuthenticatedComponenentは記述忘れがありそうでした。大抵のFlux実装とインテグできて、記述忘れも少ない方法が必要ほしくなります。

できれば、ルーティング設定で認証の制御を宣言したいと思いました。認証コンポーネントをルーティングに組み込む方法です。例えば下のようにです。

<Route component={App}>
  <Route component={UserOnly}>
    <Route path="/" component={Dashboard}/>
    <Route path="/settings" component={Settings}/>
  </Route>
  <Route component={GuestOnly}>
    <Route path="/signup" component={SignUp}/>
    <Route path="/login" component={Login}/>
  </Route>
</Route>

こうしておけば、ルーティング設定を見れば認証制御が一目瞭然で、認証まわりのコードを冗長的に書く必要もありません。

上のコードで示したUserOnlyGuestOnlyは例によってReact Componentの一種です。UserOnlyの実装サンプルが下のようになります。Flux実装のひとつのReduxを使ったものになっています。@connectの部分はReduxでストアと繋ぎこむ宣言なので、お使いのFlux実装に読み替えてください。

UserOnly.jsx
import React from "react";

import { connect } from "redux/react";

@connect(state => ({
  session: state.session
}))
export default class UserOnly {
  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  componentWillMount() {
    this.guestWillTransfer(this.props, this.context.router);
  }

  componentWillUpdate(nextProps) {
    this.guestWillTransfer(nextProps, this.context.router);
  }

  guestWillTransfer(props, router) {
    const { loggedIn } = props.session;
    if (loggedIn === false) {
      router.replaceWith("/login");
    }
  }

  render() {
    return (
      <div>{this.props.children}</div>
    );
  }
}

UserOnlyなどはコンポーネントなのでストアの状態(つまりログイン状態)が変化すると、componentWillMountcomponentWillUpdateが呼ばれます。ここでフックして、認証できなかった場合、リダイレクトしているわけです。

ついでにGuestOnlyの実装です。

GuestOnly.jsx
import React from "react";

import { connect } from "redux/react";

@connect(state => ({
  session: state.session
}))
export default class GuestOnly {
  static contextTypes = {
    router: React.PropTypes.object.isRequired
  }

  componentWillMount() {
    this.userWillTransfer(this.props, this.context.router);
  }

  componentWillUpdate(nextProps) {
    this.userWillTransfer(nextProps, this.context.router);
  }

  userWillTransfer(props, router) {
    const { loggedIn } = props.session;
    if (loggedIn === true) {
      router.replaceWith("/");
    }
  }

  render() {
    return (
      <div>{this.props.children}</div>
    );
  }
}

以上が、認証コンポーネントをルーティングに組み込んで、認証を制御する方法でした。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away