【SPA】Laravel + Vue.js + JWT認証でメールアドレス送信を行い登録するまで part.2 - inokawablog

【SPA】Laravel + Vue.js + JWT認証でメールアドレス送信を行い登録するまで part.2

前回に引き続きやっていきます。vueファイルを作成し、jwt認証までを行います。

 

環境は以下の通りです。

Node npm Vue.js Vue Router Vuex PHP Laravel
12.4.0 6.9.0 2.5.17 3.1.2 3.1.1 7.1.16 5.8.34

 

LaravelMixの設定

webpack.mix.js

const mix = require('laravel-mix')

mix.browserSync('vuesplash.test')
  .js('resources/js/app.js', 'public/js')
  .sass('resources/sass/app.scss', 'public/css')
  .version()

 

bootstrapを使用しない場合は、app.scssの以下の部分をコメントアウトしてください。

// @import '~bootstrap/scss/bootstrap';

 

resources/js/bootstrap.jsを以下のようにしてください。

window.axios = require('axios');

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

let token = document.head.querySelector('meta[name="csrf-token"]');

if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}

 

認証用のVue Jsパッケージのセットアップ

jwt認証に必要なパッケージをインストールします。

npm i @websanova/vue-auth vue-router vue-axios axios es6-promise

resources/js/auth.js を以下の内容で作成してください。

import bearer from '@websanova/vue-auth/drivers/auth/bearer'
import axios from '@websanova/vue-auth/drivers/http/axios.1.x'
import router from '@websanova/vue-auth/drivers/router/vue-router.2.x'
/**
 * Authentication configuration, some of the options can be override in method calls
 */
const config = {
  auth: bearer,
  http: axios,
  router: router,
  tokenDefaultName: 'laravel-jwt-auth',
  tokenStore: ['localStorage'],

  // API endpoints used in Vue Auth.
  registerData:   { url: 'api/auth/register',   method: 'POST',   redirect: '/login'      },
  loginData:      { url: 'api/auth/login',      method: 'POST',   redirect: '/account',     fetchUser: true     },
  logoutData:     { url: 'api/auth/logout',     method: 'POST',   redirect: '/',            makeRequest: true   },
  fetchData:      { url: 'api/auth/user',       method: 'GET',    enabled: true                                 },
  refreshData:    { url: 'api/auth/refresh',    method: 'GET',    enabled: true,            interval: 30        }
}
export default config

これらのvue-auth の構成に関して詳しくは公式ドキュメントをみてください。

 

デフォルトコンポーネントの作成

デフォルトのコンポーネントであるresources/js/App.vue を作成してください。

App.vue

<template>
  <div class="container">
    <header>
      <Header />
    </header>
    <main>
      <div class="container">
        <div class="content">
          <router-view></router-view>
        </div>
      </div>
    </main>
    <Footer />
  </div>
</template>

<script>
import Header from './components/Header.vue'
import Footer from './components/Footer.vue'

export default {
    components: {
      Header,Footer
    },

    data: () => ({
    }),
    created() {
      this.$store.dispatch('auth/setUserInfo')
    },

    methods: {
    },
}
</script>

 

Vue Routerの設定

resources/js/routerを作成し、index.jsを以下の内容で作成してください。

index.js

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

import app from '../App.vue'
Vue.component('app', app)

// Pages
import Register              from '../pages/auth/Register.vue'
import Login                 from '../pages/auth/Login.vue'

import index                     from '../pages/TopPage.vue'
import account                   from '../pages/AccountPage.vue'
import not_found                      from '../pages/NotFound.vue'

export default new Router({
  mode: 'history',
  routes: [
    { path: '/',                         name: 'index',                  component: index},
    // auth
    {path: '/register',                  name: 'register',               component: Register,              meta: { auth: false       }},
    {path: '/login',                     name: 'login',                  component: Login,                 meta: { auth: false       }},
    // user
    { path: '/account',                  name: 'account',                component: account,               meta: { auth: true         }},
    // not found
    { path: '/404',                      name: 'not_found',              component: not_found },
    { path: '*', redirect: '/404' },
  ],
})

それぞれのコンポーネントはあとで作成します。

 

Vuexの設定

resources/js/storeを作成し、index.jsを以下の内容で作成してください。

index.js

import Vue from 'vue'
import Vuex from 'vuex'

import auth from './auth'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    auth
  }
})

export default store

 

resources/js/store/auth.js を作成してください。今回はauth.js にユーザー情報を保存しておきます。

auth.js

const state = {
  user: {
    id: Number,
    name: '',
    email: '',
  },
}

const getters = {}

const mutations = {
  setUser (state, user) {
    state.user = user
  },
}

const actions = {
  async logout (context) {
    context.commit('setUser',  null)
    const response = await axios.post('/logout')
  },

  async setUserInfo(context){
    try {
      const res = await axios.get('/api/auth/user')
      if (res.data.data) {
        context.commit('setUser', {id:res.data.data.id, name:res.data.data.name, email:res.data.data.email})
      }
    } catch (e) {
      console.log(e)
    }
  }
}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

 

app.jsを更新

resources/js/app.js を以下のように変更してください。

app.js

import './bootstrap'

// Vue-Router
import router from './router'
import store from './store'
// Set Vue router
Vue.router = router

import axios from 'axios'
import VueAxios from 'vue-axios'
import 'es6-promise/auto'
import VueAuth from '@websanova/vue-auth'
import auth from './auth'
Vue.use(VueAxios, axios)
Vue.use(VueAuth, auth)

import Vue from 'vue'

new Vue({
  el: '#app',
  router,
  store
})

 

コンポーネントの作成

resources/js/pages/TopPage.vueトップページを作成します。

TopPage.vue

<template>
  <div class="content">
    <p>トップページ</p>
  </div>
</template>

 

次にログインした後のアカウント用ページresources/js/pages/Account.vue を作成します。

Account.vue

<template>
  <div class="account-page-wrap">
    <div class="account-page-content">
      <div class="account-content">
        <div class="account-head">
          <h1>Your Accouts</h1>
          <div class="account-logout" v-on:click="logout">
            <p>sign out</p>
          </div>
        </div>
        <div class="account-info">
          <div class="account-section">
            <h2>Mail</h2>
            <p>{{$store.state.auth.user.email}}</p>
          </div>
          <div class="account-section">
            <h2>Name</h2>
            <p>{{$store.state.auth.user.name}}</p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  created() {
    this.$store.dispatch('auth/setUserInfo')
  },
  methods: {
    logout() {
      this.$auth.logout()
    },
  },
}
</script>

 

ヘッダーのコンポーネントを作成

resources/js/components/Header.vue

Header.vue

<template>
  <nav class="navbar spa_head">
    <div class="navbar__menu">
      <div class="navbar-logo">
        <router-link to="/">
          <p>Home</p>
        </router-link>
      </div>
    </div>
  </nav>
</template>

<script>
  export default {
    data() {
      return {
        routes: {
          // UNLOGGED
          unlogged: [
            { name: 'Register', path: 'register' },
            { name: 'Login', path: 'login'}
          ],
          // LOGGED USER
          user: [
            { name: 'Dashboard', path: 'dashboard' }
          ],
          // LOGGED ADMIN
          admin: [
            { name: 'Dashboard', path: 'admin.dashboard' }
          ]
        }
      }
    },

    methods: {
      logout() {
        var app = this
        this.$auth.logout()
      }
    }
  }
</script>

 

フッターのコンポーネントを作成

resources/js/components/Footer.vue

Footer.vue

<template>
  <footer class="footer">
    <p>footer</p>
  </footer>
</template>

 

認証周りのvueファイルを作成していきます。

/resources/js/pages/auth ディレクトリを作成し、register.vueを作成します。

Register.vue

<template>
  <div class="container">
    <div class="row justify-content-center">
      <div class="col-md-8">
        <div class="card-header">
          <router-link :to="{ name: 'register', params: {} }">Register</router-link>
          <router-link :to="{ name: 'login', params: {} }">Login</router-link>
        </div>
        <div class="card">
          <div class="half-color"></div>
          <div class="card-body">
            <form autocomplete="off" @submit.prevent="register" v-if="!success" method="post">

              <div class="form-group row" v-bind:class="{ 'has-error': has_error && errors.name }">
                <label for="name">Name</label>
                <div class="col-md-6">
                  <input type="text" id="name" class="form-control" placeholder="Full Name" v-model="name" autocomplete="name" autofocus>
                  <span class="invalid-feedback" role="alert" v-if="has_error && errors.name">
                    <strong>{{ errors.name }}</strong>
                  </span>
                </div>
              </div>

              <div class="form-group row" v-bind:class="{ 'has-error': has_error && errors.email }">
                <label for="email">E-mail</label>
                <div class="col-md-6">
                  <input type="email" id="email" class="form-control" placeholder="user@example.com" v-model="email" required autocomplete="email">
                  <span class="invalid-feedback" role="alert" v-if="has_error && errors.email">
                    <strong>{{ errors.email }}</strong>
                  </span>
                </div>
              </div>

              <div class="form-group row" v-bind:class="{ 'has-error': has_error && errors.password }">
                <label for="password">Password</label>
                <div class="col-md-6">
                  <input type="password" id="password" class="form-control" v-model="password" required autocomplete="new-password">
                  <span class="invalid-feedback" role="alert" v-if="has_error && errors.password">
                    <strong>{{ $message }}</strong>
                  </span>
                </div>
              </div>

              <div class="form-group row" v-bind:class="{ 'has-error': has_error && errors.password }">
                <label for="password_confirmation">Password confirmation</label>
                <div class="col-md-6">
                  <input type="password" id="password_confirmation" class="form-control" v-model="password_confirmation" required autocomplete="new-password">
                </div>
              </div>

              <div class="form-group row mb-0">
                <div class="col-md-6 offset-md-4">
                  <button type="submit" class="form-submit btn btn-primary">Submit</button>
                </div>
              </div>

            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        name: '',
        email: '',
        password: '',
        password_confirmation: '',
        has_error: false,
        error: '',
        errors: {},
        success: false
      }
    },
    methods: {
      register() {
        var app = this
        this.$auth.register({
          data: {
            name: app.name,
            email: app.email,
            password: app.password,
            password_confirmation: app.password_confirmation
          },
          success: function () {
            app.success = true
          },
          error: function (res) {
            app.has_error = true
            app.error = res.response.data.error
            app.errors = res.response.data.errors || {}
          }
        })
      }
    }
  }
</script>

 

次に、ログイン用のLogin.vueを作成します。

Login.vue

<template>
  <div class="container">
    <div class="row justify-content-center">
      <div class="col-md-8">
        <div class="card-header">
          <router-link :to="{ name: 'register', params: {} }">Register</router-link>
          <router-link :to="{ name: 'login', params: {} }">Login</router-link>
        </div>
        <div class="card">
          <div class="card-body">
            <div class="alert alert-danger" v-if="has_error && !success">
              <p>{{error}}</p>
            </div>
            <form autocomplete="off" @submit.prevent="login" method="POST">
              <div class="form-group row">
                <label for="email" class="col-md-4 col-form-label text-md-right">E-mail</label>
                <div class="col-md-6">
                    <input type="email" id="email" class="form-control" placeholder="user@example.com" v-model="email" required>
                </div>
              </div>

              <div class="form-group row">
                <label for="password" class="col-md-4 col-form-label text-md-right">Password</label>
                <div class="col-md-6">
                  <input type="password" id="password" class="form-control" v-model="password" required>
                </div>
              </div>

              <div class="form-group row mb-0">
                <div class="col-md-8 offset-md-4">
                  <button type="submit" class="form-submit btn btn-primary">Signin</button>
                </div>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
  export default {
    data() {
      return {
        email: null,
        password: null,
        success: false,
        has_error: false,
        error: '',
      }
    },
    methods: {

      login() {
        // get the redirect object
        var redirect = this.$auth.redirect()
        var app = this
        this.$auth.login({
          data: {
            email: app.email,
            password: app.password
          },
          success: function() {
            app.success = true
          },
          error: function(res) {
            app.has_error = true
            app.error = res.response.data.error
          },
          rememberMe: true,
          fetchUser: true
        })
      }
    }
  }
</script>

this.$auth.loginはresources/auth.jsで定義されているloginです。

 

404ページ

/resources/js/pages/NotFound.vueを作成します。

<template>
  <div class="not-found-page">
    <p>404 Not Found</p>
  </div>
</template>

 

resources/views/welcome.blade.php をindex.blade.phpにファイル名を変更し、内容を以下のように書き換えてください。

index.blade.php

<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="csrf-token" content="{{ csrf_token() }}">
  <title>{{ config('app.name', 'Laravel') }}</title>
  <link rel="dns-prefetch" href="//fonts.gstatic.com">
  <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
  
  <link href="{{ mix('css/app.css') }}" rel="stylesheet">
</head>
  <body>
    <div id="app">
      <app></app>
    </div>
    <script src=" {{ mix('js/app.js') }} "></script>
  </body>
</html>

App.vueを出力しています。

 

ルートの設定

IndexController.php を以下の内容で作成してください。index.blade.php を返すためだけに作成します。

php artisan make:controller IndexController

IndexController.php

<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index()
  {
    return view('index');
  }
}

 

全てのパスをindex.blade.phpに通します。

web.phpを以下のように変更してください。

web.php

<?php
Route::get('/{any?}', 'IndexController@index')->where('any', '^(?!api\/)[\/\w\.-]*')->name('index');

 

最後にサーバーを立ち上げ、ファイルをコンパイルします。

php artisan serve

npm run watch

 

試しに会員登録を行なってログインしてみてください。アカウントページが表示されれば成功です。

 


 

これで一通りのjwt認証は終了です。

次回はemailを送信し、仮登録を行い本登録をするまでを行います。