うしろのこの本ください

なんでもかきます

Role-Based Access Control (RBAC) をVue.jsで表現する

元ネタ

auth0.com

最近仕事で権限ごとに表示できるコンポーネントを制御する必要がでてきて、さてどうするかというタイミングでチームメンバーがRBACのことを教えてくれた。

Roleは1つ以上の権限を持ち、権限はコンポーネントの表示を制御する。まあ難しい考えかたは必要なくて、単にロールと権限の定義と、ログインユーザーのロール、コンポーネントを表示できる権限があれば簡単に実装できる。

以下は動的な制御が不要な場合の例

rbac.js

import rules from "./rbac-rules"

const check = (role, action) => {
  const permissions = rules[role]
  if (!permissions) {
    // ロールが存在しないためfalse
    return false
  }

  if (permissions.includes(action)) {
    // ロールが渡された権限を持つためtrue
    return true
  }

  return false
}

export default check

rbac-rules.js

export default {
  visitor: [
    'contents_read'
  ],
  writer: [
    'contents_read',
    'contents_update'
  ],
  admin: [
    'contents_read',
    'contents_update',
    'user_update'
  ]
}

Vueではslotを使ってコンポーネントを渡し、中でcheckで権限を見て出し分けると良い感じになる。そのためのラッパーコンポーネントを作る。

Can.vue

<template>
  <div>
    <slot v-if="checkRule" name="yes" />
    <slot v-else name="no" />
  </div>
</template>

<script>
import check from '~/auth/rbac'

export default {
  props: {
    role: {
      type: String
    },
    action: {
      type: String
    }
  },
  computed: {
    checkRule() {
      return check(this.role, this.action)
    }
  }
}
</script>

これで一応コンポーネント2つ渡せば権限NGだった時に任意のコンポーネントにフォールバックさせることができる。不要なら2つ目は渡さなくても良い。

Can.vueを使った例

Hoge.vue

<template> 
 <div>
    <Can role="visitor" action="contents_read">
      <template #yes>
        <Text text="権限あるよ" />
      </template>
      <template #no>
        <Text text="権限ないよ" />
      </template>
    </Can>
  </div>
</template>

<script>
import Can from '~/components/Can'
import Text from '~/components/Text'
export default {
  components: {
    Can,
    Text
  }
}

今回の場合ロールが複数あると判定できないので、ロールを配列で渡してキーとマッチングして取れた配列を単一の配列にマージする、もしくは同じことをするreducerを書くとかする必要がある。ロールはpropsじゃなくてCanからstoreを見るなりしても良い。

また、動的な権限制御が必要な時は定義にstaticとdynamicの概念を追加する必要がある。元ネタの方に載っているので興味ある人は実装してみると良いと思う。

おわり