Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
240
Help us understand the problem. What is going on with this article?
@suin

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

More than 5 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>
    );
  }
}

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

240
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  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
suin
Qiita 4位/TypeScript入門書執筆中/TypeScripterのための座談会「YYTypeScript」主催/『実践ドメイン駆動設計』書籍邦訳レビュア/分報Slack考案/YYPHP主催/CodeIQマガジン執筆/株式会社クラフトマンソフトウェア創設/Web自動テスト「ShouldBee」の開発/TypeScript/DDD/OOP
craftsman_software
「インフラの心配は、もうおしまい」 インフラ運用を自動化し、手作業を限りなくゼロにする会社

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
240
Help us understand the problem. What is going on with this article?