[Laravel5.5でREST API + SPA] API認証を実装する (JWTAuth) フロントエンド編

バックエンド側の認証制御は実装できたので
次はフロントエンドにログイン機能を実装していきます。

vuexを入れてユーザ情報を一元管理する

vuexとは、vue-routerと同じくVue.jsのオプションパッケージで
SPAアプリケーション全体で保持する状態やデータを管理するライブラリです。
たとえば全コンポーネントから参照されるログイン中のユーザ情報などを扱うのに適しています。

 

$ npm install vuex --save-dev

npmでインストールします。

 

resources/assets/js/store/ディレクトリを作り、以下の3ファイルを作成します。

import http from "../services/http";

const Vuex = require('vuex');
const {MUTATION} = require('./mutation-types');
const {ACTION} = require('./action-types');

const state = {
  me: null,
};

const getters = {
  me: (state) => {
    return state.me;
  }
};

const actions = {
  [ACTION.LOGIN]({commit}, {email, password, successCb, errorCb}) {
    let loginParams = {email: email, password: password}
    http.post('authenticate', loginParams, res => {
      commit(MUTATION.SET_ME, {me: res.data.user});
      successCb(res);
    }, error => {
      errorCb(error);
    });
  },
  [ACTION.LOGOUT]({commit}, {successCb, errorCb}) {
    http.get('logout', () => {
      successCb();
    }, error => {
      errorCb(error);
    });
  },
  [ACTION.ME]({commit}, {successCb, errorCb}) {
    http.get('me', res => {
      commit(MUTATION.SET_ME, {me: res.data.user});
      successCb(res);
    }, error => {
      errorCb(error);
    });
  }
};

const mutations = {
  [MUTATION.SET_ME](state, {me, token}) {
    state.me = me;
    console.log('MUTATION.SET_ME', this.state);
  },
  [MUTATION.REMOVE_ME](state) {
    state.me = null;
    localStorage.removeItem('jwt-token');
    console.log('MUTATION.REMOVE_ME', this.state);
  }
};

export default new Vuex.Store({
  state,
  getters,
  actions,
  mutations
});
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
export const ME = 'ME';

export const ACTION = {
  LOGIN,
  LOGOUT,
  ME
};
export const SET_ME = 'SET_ME';
export const REMOVE_ME = 'REMOVE_ME';

export const MUTATION = {
  SET_ME,
  REMOVE_ME
};

index.js内の「state」が保持するデータ、「getters」がその名の通りデータのgetterです。
「actions」が各コンポーネントから呼び出されるメソッドになりますが、
「mutations」だけがstateを変更できるという構造になっています。
こうすることで、stateデータは必ずこの処理を通して扱われる = 一元管理されることとなります。
これをStoreパターンと呼びます。

今回は認証処理とそこで取得されるログインユーザ情報をStoreで管理する形になります。
ログインアクションが成功したらユーザ情報をミューテーションでstateに記憶し、
ログアウトしたらその逆、といった実装になっています。

 

ログイン画面 / ログアウト処理

<template>
    <div>
        <h1>Login</h1>
        <div v-if="showAlert" style="color: #FF9999;">{{ alertMessage }}</div>
        <label for="email">E-Mail Address</label>
        <div>
            <input id="email" type="email" v-model="email" @keyup.enter="login" required autofocus>
        </div>
        <label for="password">Password</label>
        <div>
            <input id="password" type="password" v-model="password" @keyup.enter="login" required autofocus>
        </div>
        <button @click="login">Login</button>
    </div>
</template>
<script>
  const store = require('../store/').default;
  const {LOGIN} = require('../store/action-types');

  export default {
    mounted() {
    },
    data() {
      return {
        email: '',
        password: '',
        showAlert: false,
        alertMessage: '',
      }
    },
    methods: {
      login() {
        let params = {
          email: this.email,
          password: this.password,
          successCb: res => {
            console.log('logged in.', res);
            this.$router.push('/users/')
          },
          errorCb: error => {
            this.showAlert = true;
            this.alertMessage = 'Wrong email or password.'
          }
        };
        store.dispatch(LOGIN, params);
      },
    }
  }
</script>

vuexで作成したstoreを使ってログインアクションをdispatchします。
ログインに成功したら/users/にリダイレクト。

 

<template>
</template>
<script>
  const store = require('../store/').default;
  const {LOGOUT} = require('../store/action-types');

  export default {
    mounted() {
      let params = {
        successCb: res => {
          console.log('logged out.');
          this.$router.push('/login')
        },
        errorCb: error => {
          console.log(error);
        }
      };
      store.dispatch(LOGOUT, params);
    },
    data() {
      return {
      }
    },
    methods: {
    }
  }
</script>

こちらはログアウト処理。
コンポーネントにする必要はないのですが、
わかりやすいので空のコンポーネントにログアウト処理を書いて
最後にログイン画面にリダイレクトしています。

 

ルーティング

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

・・・

const store = require('./store/').default;

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/users', component: require('./components/Users.vue'), children: [
        {path: '/users/', component: require('./components/Users/Index.vue'), meta: {requiredAuth: true}},
        {path: '/users/edit/', component: require('./components/Users/Edit.vue'), meta: {requiredAuth: true}},
        {path: '/users/edit/:id', component: require('./components/Users/Edit.vue'), meta: {requiredAuth: true}},
      ],
      meta: {requiredAuth: true}
    },
    {path: '/login', component: require('./components/Login.vue')},
    {path: '/logout', component: require('./components/Logout.vue')},
  ]
});
router.beforeEach ((to, from, next) => {
  if (!store.getters.me) {
    http.init();
    store.dispatch(ME, {
      successCb: res => {
        console.log('me res', res);
        next();
      },
      errorCb: error => {
        console.log('me error', error);
        if (to.matched.some(record => record.meta.requiredAuth)) {
          next({path: '/login', query: {redirect: to.fullPath}});
        } else {
          next();
        }
      }
    });
  } else {
    next();
  }
});

const app = new Vue({
  router,
  store,
  created() {
    http.init();
  },
  el: '#app'
});

/loginと/logoutをルーティングに追加。
router.beforeEachは新しいルートに遷移したすべてのタイミングで走る処理を登録する関数です。
ここで、storeにログインユーザ情報があるか確認し、なければ/meに問い合わせて
トークンが生きているか(ログイン中か)を画面遷移の度に確認します。
どの画面を要認証とするかは、routes設定のmeta.requiredAuthで指定します。

 

リクエストヘッダーにJWTトークンを付与

export default {
  isInited: false,

・・・

  /**
   * Init the service.
   */
  init () {
    if (this.isInited) return;

    this.isInited = true;
    axios.defaults.baseURL = '/api'

    // Intercept the request to make sure the token is injected into the header.
    axios.interceptors.request.use(config => {
      config.headers['Authorization']    = `Bearer ${localStorage.getItem('jwt-token')}`;
      return config
    })

    // Intercept the response and ...
    axios.interceptors.response.use(response => {
      // ...get the token from the header or response data if exists, and save it.
      const token = response.headers['Authorization'] || response.data['token'];
      if (token) {
        localStorage.setItem('jwt-token', token)
      }

      return response
    }, error => {
      // Also, if we receive a Bad Request / Unauthorized error
      console.log(error)
      return Promise.reject(error)
    })
  }
}

APIへのリクエストを一括管理しているhttpサービスを上記のように変更します。
レスポンスにjwtトークンが含まれていればローカルストレージに保存し、
リクエスト時にそのトークンを自動付与してAPIをコールする仕組みです。

 

 

これでフロントからログインしてAPIを利用できるようになりました。

コメントを残す

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

関連する投稿

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

トップに戻る