[Laravel5.5でREST API + SPA] Vue.js/vue-routerでのページごとの認証(Role判定つき)

前提

これまでの記事で、LaravelでAPIサーバをつくり
JWTAuthを使ってRole機能付きの認証を実装してきました。

[Laravel5.5でREST API + SPA] API基本構築その1
[Laravel5.5でREST API + SPA] API基本構築その2
[Laravel5.5でREST API + SPA] Vue.jsでUI作成
[Laravel5.5でREST API + SPA] API認証を実装する (JWTAuth) バックエンド編
[Laravel5.5でREST API + SPA] JWTAuthでのAPI認証にRole機能を追加する

既にAPI側で認証がかかっているので
見られてはいけないデータが見えてしまうことはない状態にはなっていますが
ページ自体にも認証をかけてログインページへリダイレクトしたいですよね。
今回はそのやり方です。

 

vue-routerでページ遷移ごとの認証を設定

router.js全体は以下のようになっています。

import Vuerouter from 'vue-router'
import Vue from 'vue'
import http from "./services/http";

Vue.use(Vuerouter);
Vue.use(require('vuex'));

const store = require('./store/').default;
const {ME} = require('./store/action-types');

const router = new Vuerouter({
  mode: 'history',
  routes: [
    {
      path: '/admin', component: require('./components/Admin.vue'), children: [
        {path: '/admin/login', component: require('./components/Admin/Login.vue')},
        {path: '/admin/logout', component: require('./components/Admin/Logout.vue')},
        {
          path: '/admin/users', component: require('./components/Admin/Users.vue'), children: [
            {
              path: '/admin/users/',
              component: require('./components/Admin/Users/Index.vue'),
              meta: {required_auth_level: 99}
            },
            {
              path: '/admin/users/edit/',
              component: require('./components/Admin/Users/Edit.vue'),
              meta: {required_auth_level: 99}
            },
            {
              path: '/admin/users/edit/:id',
              component: require('./components/Admin/Users/Edit.vue'),
              meta: {required_auth_level: 99}
            },
          ],
          meta: {required_auth_level: 99}
        },
      ]
    },
  ]
});

const getRequiredAuthLevel = function (routes) {
  let ret = 0;
  routes.forEach((route) => {
    let level = route.meta.required_auth_level;
    if (level > ret) ret = level;
  });
  return ret;
};

const getMe = function (cbFunc) {
  if (store.getters.me) {
    cbFunc(store.getters.me);
  } else {
    http.init();
    store.dispatch(ME, {
      successCb: res => {
        let me = res.data.user;
        cbFunc(me);
      },
      errorCb: error => {
        cbFunc(null);
      }
    });
  }
};

const isAuthenticated = function (me, level) {
  let role = me && me['role'] ? me['role'] : -1;
  return (role >= level);
};

router.beforeEach((to, from, next) => {
  let requiredAuthLevel = getRequiredAuthLevel(to.matched);
  if (getRequiredAuthLevel(to.matched) > 0) {
    // 認証ありのページの場合
    getMe((me) => {
      if (isAuthenticated(me, requiredAuthLevel)) {
        // 認証OK
        next();
      } else {
        // 認証NG
        if (requiredAuthLevel >= 99) {
          next({path: '/admin/login', query: {redirect: to.fullPath}});
        } else {
          next({path: '/login', query: {redirect: to.fullPath}});
        }
      }
    });
  } else {
    // 認証なしのページの場合
    next();
  }
});

export default router;

以下、ブロックにわけて解説していきます。

 

ルーティング部分

バックオフィス(管理画面)を想定したルーティングにしています。
childrenプロパティを使って入れ子にしているので、ちょっとわかりづらくてすいません。

const router = new Vuerouter({
  mode: 'history',
  routes: [
    {
      path: '/admin', component: require('./components/Admin.vue'), children: [
        {path: '/admin/login', component: require('./components/Admin/Login.vue')},
        {path: '/admin/logout', component: require('./components/Admin/Logout.vue')},
        {
          path: '/admin/users', component: require('./components/Admin/Users.vue'), children: [
            {
              path: '/admin/users/',
              component: require('./components/Admin/Users/Index.vue'),
              meta: {required_auth_level: 99}
            },
            {
              path: '/admin/users/edit/',
              component: require('./components/Admin/Users/Edit.vue'),
              meta: {required_auth_level: 99}
            },
            {
              path: '/admin/users/edit/:id',
              component: require('./components/Admin/Users/Edit.vue'),
              meta: {required_auth_level: 99}
            },
          ],
          meta: {required_auth_level: 99}
        },
      ]
    },
  ]
});

認証実装に関係ないコードの分量が多くて申し訳ないですが、
ここで認証に絡んでいるのは「required_auto_level」というプロパティだけです。
metaキーでこういう任意のパラメータをルートに持たせることができるので、
ここで、このページを見るために必要なrole権限を指定しています。
99のロール(=管理者権限)を持ったユーザでなければページ遷移を許可しない、という形です。

 

認証関連の関数

beforeEachの中で直線的に処理を書くとかなりゴチャっとなってしまうので、
適宜まとめて関数に切り出しています。

const getRequiredAuthLevel = function (routes) {
  let ret = 0;
  routes.forEach((route) => {
    let level = route.meta.required_auth_level;
    if (level > ret) ret = level;
  });
  return ret;
};

これはそのページでどのレベルの権限が必要されているかを返す関数です。
引数のroutesにはnew Vuerouterで指定したルートオブジェクトが配列で入ってきます。

たとえば/admin/users/edit/ にアクセスした場合、

[
  {path: '/admin/'},
  {path: '/admin/users/', meta: {required_auth_level: 99}},
  {path: '/admin/users/edit/', meta: {required_auth_level: 99}}
]

こんな感じで親のルートも含まれてきます。
この中で一番大きなrequired_auth_levelを抽出して返すのがこの関数の役割になっています。

 

const getMe = function (cbFunc) {
  if (store.getters.me) {
    cbFunc(store.getters.me);
  } else {
    http.init();
    store.dispatch(ME, {
      successCb: res => {
        let me = res.data.user;
        cbFunc(me);
      },
      errorCb: error => {
        cbFunc(null);
      }
    });
  }
};

これはログインユーザ情報を取得する関数です。
vuexのストアに既に存在していればそれを返し、
存在していなければストア経由でAPIに問い合わせてみる、という処理です。

 

const isAuthenticated = function (me, level) {
  let role = me && me['role'] ? me['role'] : -1;
  return (role >= level);
};

そしてこれが認証判定する関数ですね。
ユーザオブジェクトのroleフィールドを見て判定しているだけです。

 

ページ遷移ごとの認証処理部分

router.beforeEach((to, from, next) => {
  let requiredAuthLevel = getRequiredAuthLevel(to.matched);
  if (getRequiredAuthLevel(to.matched) > 0) {
    // 認証ありのページの場合
    getMe((me) => {
      if (isAuthenticated(me, requiredAuthLevel)) {
        // 認証OK
        next();
      } else {
        // 認証NG
        next({path: '/admin/login', query: {redirect: to.fullPath}});
      }
    });
  } else {
    // 認証なしのページの場合
    next();
  }
});

router.beforeEachでページ遷移イベントを登録します。
「to」という引数に、次に遷移する予定のルートオブジェクトが配列で入っているわけですね。

うまいこと関数に切り出せたので、かなりスッキリ書くことができたんじゃないかと思います。

これでフロントエンド側にも認証を実装することができました。
ご参考になれば幸いです。

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連する投稿

検索語を上に入力し、 Enter キーを押して検索します。キャンセルするには ESC を押してください。

トップに戻る