LoginSignup
4
8

More than 5 years have passed since last update.

react-router でアクセス制御

Last updated at Posted at 2016-11-23

TL;DR

やりたいこと

ユーザーの権限やロールなどによる、 react-router で定義した route へのアクセス制御

やらないこと

使用しているライブラリなどの解説

前提

  • ES2015
  • reactjs v15.x.x
  • react-router v2.8.x
  • flux

ユーザーの属性はアプリケーションの仕様によるので、今回はユーザーの「強さ」によってアクセスの可不可を決める。

各自、開発するアプリケーションで使用する言葉に置き換えて下さい。

ユーザーの強さ(Strength)一覧

  • yowai: 初級者
  • hutuu: 中級者
  • tsuyoi: 上級者
  • saikyou: 最強

Route 一覧

  • /
  • /doujo
  • /doujo/yowai
  • /doujo/hutuu
  • /doujo/tsuyoi
  • /doujo/saikyou
  • /doujo/yowai/promotion
  • /doujo/hutuu/promotion
  • /doujo/tsuyoi/promotion

各 Route に入れる人

  • /
    • 世界
    • 大体 App みたいなのを指定してると思うので、今回もそうします。
  • /doujo
    • 道場の入り口
    • 誰でも入れる
  • /doujo/:tsuyosa
    • 自分が属する強さと、1つ下の道場に入ることが出来る
    • 例:中級者なら hutuu と yowai
  • /doujo/:tsuyosa/promotion
    • 昇級試験
    • 自分が属する強さの試験しか受けることが出来ない

アクセス制御前のコード

hutuu 以上はだいたい同じなので省略👷

import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Router, browserHistory } from 'react-router';

class App extends React.Component {
  static propTypes = {
    children: React.PropTypes.node,
  };

  render() {
    return this.props.children;
  }
}

class Doujo extends React.Component {
  static propTypes = {
    children: React.PropTypes.node,
  };
  render() {
    return (<div>
      <h1>道場</h1>
      {this.props.children}
    </div>);
  }
}

class YowaiDoujo extends React.Component {
  static propTypes = {
    children: React.PropTypes.node,
  };
  render() {
    return (<div>
      <h2>初級者向け道場</h2>
      {this.props.children}
    </div>);
  }
}

class YowaiPromotionTest extends React.Component {
  static propTypes = {
    children: React.PropTypes.node,
  };
  render() {
    return (<div>
      <h3>初級者昇級試験</h3>
      {this.props.children}
    </div>);
  }
}

const routes = (
  <Route path="/" component={App}>
    <Route path="/doujo" component={Doujo}>
      <Route path="yowai" component={YowaiDoujo}>
        <Route path="promotion" component={YowaiPromotionTest} />
      </Route>
    </Route>
  </Route>
);

const router = (
  <Router history={browserHistory}>
    {routes}
  </Router>
);

ReactDOM.render(
  router,
  document.getElementById('app')
);

アクセス制御を実装する

ユーザーの強さはサーバーに問い合わせて取得するということにする。

Route の設定

以下のように Route に tsuyosa を指定していく

const routes = (
  <Route path="/" component={App}>
    <Route path="/doujo" component={Doujo}>
      <Route path="yowai" component={YowaiDoujo} tsuyosa={['yowai', 'hutuu']}>
        <Route path="promotion" component={YowaiPromotionText} tsuyosa={['yowai']} />
      </Route>
    </Route>
  </Route>
);

アクセス制御

アクセス制御の実装は Higher Order Component (Inheritance Inversion) パターン(?)を使って実装する。

ReactのHigher Order Components詳解 : 実装の2つのパターンと、親Componentとの比較 | プログラミング | POSTD

HOC については↑これを読んで下さい。

何故 HOC を使うかについては端折るけど、結果として以下2つの良い点があった。

  • アプリケーションの大本の Component からアクセス制御の実装を完全に切り離せる
  • ルーティングの設定のところに withAuthorizationtsuyosa を書けるので、アクセス制御の設定などを一箇所にまとめられる

HOC の用法として適切なのかについては、各自で判断して下さい。

実装

  • Flux の「 Action -> Dispatcher -> Store -> ChangeEvent 」の流れを setTimeout で代用する
  • 起動時に constructor が呼ばれるけど、サーバーとの通信前なので Route の情報を取っておいて後で処理する
    • startupRoute
    • 使い終わったら null にする
  • routes から設定した tsuyosa を取得するのに getTuyosa という関数を実装したけど、実装がデカイので gist に上げた
function withAuthorization(WrappedComponent) {
  return class AuthorizationComponent extends WrappedComponent {
    constructor(...args) {
      super(...args);

      this.state = {
        // 初期化処理が終わったか判定するために userStrength の初期値は undefined にする
        userStrength: undefined,
        isAuthorized: true,
        // 起動時は userStrength が不明なので、あとで route にアクセス可能か判定する
        startupRoute: {
          routes: this.props.routes,
          location: this.props.location,
        },
      };

      // 今回はサーバー通信出来ないので、 `setTimeout` で代用する
      // 実際は Store からの Change Event を受け取って、`setState` を呼ぶ
      setTimeout(() => {
        this.setState({
          userStrength: 'tsuyoi',
        });
      }, 1000);
    }

    // <Link> タグでの移動時の制御
    componentWillReceiveProps(nextProps) {
      if (super.componentWillReceiveProps) { super.componentWillReceiveProps(nextProps); }

      const { routes, location } = nextProps;
      this.setState({
        isAuthorized: this.isAuthorized(routes, location, this.state.userStrength),
      });
    }

    // 起動時の制御
    componentWillUpdate(nextProps, nextState) {
      if (super.componentWillUpdate) { super.componentWillUpdate(nextProps, nextState); }

      if (!nextState.startupRoute) { return; }
      if (this.state.userStrength === undefined) { return; }

      const { routes, location } = nextState.startupRoute;
      nextState.isAuthorized = this.isAuthorized(routes, location, this.state.userStrength);
      nextState.startupRoute = null;
    }

    isAuthorized(routes, location, strength) {
      const tsuyosa = getTuyosa(routes, location.pathname);
      if (!tsuyosa) { return true; }
      return tsuyosa.includes(strength);
    }

    render() {
      if (this.state.userStrength === undefined) { return <h1>準備中</h1>; }
      if (!this.state.isAuthorized) { return <h1>君は入れない</h1>; }
      return super.render();
    }
  };
}

Route の設定で使う

以下のように withAuthorization を使う

const routes = (
  <Route path="/" component={withAuthorization(App)}>
    <Route path="/doujo" component={Doujo}>
      <Route path="yowai" component={YowaiDoujo} tsuyosa={['yowai', 'hutuu']}>
        <Route path="promotion" component={YowaiPromotionText} tsuyosa={['yowai']} />
      </Route>
    </Route>
  </Route>
);

これで、 tsuyoi 人が /doujo/yowai/promotion にアクセスしようとしたら「君は入れない」と表示されるようになる。

getTuyosa は子 Route に tsuyosa を指定してなかったら、親の tsuyosa を取りに行くようになってるのだけど、強さとか道場とかでいい例思いつかなくて書いてない。

おしまい。

4
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
8