前提
これまでの記事で、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」という引数に、次に遷移する予定のルートオブジェクトが配列で入っているわけですね。
うまいこと関数に切り出せたので、かなりスッキリ書くことができたんじゃないかと思います。
これでフロントエンド側にも認証を実装することができました。
ご参考になれば幸いです。