WEBOPIXEL

Laravel + JWTAuth + Vue.js でAPIログイン認証の実装

Posted: 2018.10.11 / Category: PHP / Tag: 

Laravel + JWTAuthでAPIログインを実装して、Vue + VueRouter で簡易的に使用するところまでやってみます。

Sponsored Link

使用環境
Laravel 5.7
jwt-auth 1.0.0-rc3
Vue 2.5
vue-router 3.0

Laravel認証の設定

最初にLaravel側を作っていきましょう。
artisanで標準のWeb認証機能をサクッと作ります。
ターミナルで下記コマンドを実行します。

$ php artisan make:auth
$ php artisan migrate

シーダーでダミーのユーザー情報も登録しておきます。

database/seeds/UsersTableSeeder.php

<?php
use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder
{
	public function run()
	{
		DB::table('users')->insert([
			[
				'name' => 'admin',
				'email' => 'admin@example.com',
				'password' => bcrypt('password'),
				'remember_token' => null,
				'created_at' => '2018-10-02 14:28:19',
				'updated_at' => '2018-10-02 14:28:19'
			]
		]);
	}
}

database/seeds/DatabaseSeeder.php

<?php
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
	public function run()
	{
		$this->call(UsersTableSeeder::class);
	}
}
$ php artisan db:seed

これでデータベースにユーザー情報が登録され「admin@example.com」「password」の情報でログインできるようになります。

JWTAuthのインストールと設定

composerでインストールします。
Laravel 5.7 の場合はRC3を指定します。

$ composer require tymon/jwt-auth 1.0.0-rc3

次に設定ファイルを生成します。
下記コマンドを実行すると、config/jwt.phpが作成されます。

$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

下記コマンドでシークレットキーを生成します。

$ php artisan jwt:secret

.envJWT_SECRETが追記されているのを確認します。

モデル・コントローラーの修正

UserモデルにJWTSubjectインターフェイスを実装します。
メソッドはgetJWTIdentifiergetJWTCustomClaimsを作成します。

app/User.php

<?php
namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements JWTSubject
{
	use Notifiable;

	protected $fillable = [
		'name', 'email', 'password',
	];

	protected $hidden = [
		'password', 'remember_token',
	];

	public function getJWTIdentifier()
	{
		return $this->getKey();
	}

	public function getJWTCustomClaims()
	{
		return [];
	}
}

認証にJWTを使用するようにconfig/auth.phpを修正します。

config/auth.php

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],

// ...

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

新しくAuthControllerを作成します。

app/Http/Controllers/AuthController.php

<?php
namespace App\Http\Controllers;

class AuthController extends Controller
{
	function login() {
		$credentials = request(['email', 'password']);

		if (!$token = auth('api')->attempt($credentials)) {
			return response()->json(['error' => 'Unauthorized'], 401);
		}

		return $this->respondWithToken($token);
	}

	public function logout()
    {
        auth()->logout();
        return response()->json(['message' => 'ログアウトしました。']);
    }

	public function me()
	{
		return response()->json(auth()->user());
	}

	protected function respondWithToken($token)
	{
		return response()->json([
			'access_token' => $token,
			'token_type' => 'bearer',
			'expires_in' => auth("api")->factory()->getTTL() * 60
		]);
	}
}

今回はログイン・ログアウトとログイン情報を取得する機能だけ作成します。
次に、このコントローラーにアクセスするためのAPIルーターを設定します。

routes/api.php

Route::post('/login', 'AuthController@login');

Route::group(['middleware' => 'auth:api'], function () {
	Route::get('/me', 'AuthController@me');
	Route::get('/logout', 'AuthController@logout');
});

loginはログインするためのAPIなので誰でもアクセスできます。
meはログインした状態でしかアクセスできないようにmiddlewareauth:apiを指定します。

ということでここまではほとんどjwt-authのドキュメントのままだったりします。

jwt-auth

ビューファイルの作成

今回はSPAなのでbladeファイルは一つだけ作成します。

resources/views/app.blade.php

<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>LaravelSPA</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link href="{{ mix('/css/app.css') }}" rel="stylesheet">
    <meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body>
<div id="app">
   <app></app>
</div>
<script src="{{ mix('/js/app.js') }}"></script>
</body>
</html>

webルーターは全てのアクセスでこのapp.blade.phpが読み込まれるように変更します。

routes/web.php

Route::any('{all}', function () {
	return view('app');
})->where(['all' => '.*']);

Vue Router のインストール

バックエンドはだいたいできましたということで、フロント(Vue)側を作成していきます。
Laravelはpackage.jsonが最初から設定されてますので、NodeモジュールをインストールするだけでVue.jsの開発環境が整います。

$ npm install

今回は VueRouter でフロント側のルーテイングしますのでインストールしましょう。

$ npm install vue-router

下記コマンドを実行してファイルを監視状態にして開発を進めます。

$ npm run watch

ストアの作成

ログイン状態を保持するためにストアファイルを作成します。
この部分はとりあえずな感じで簡易的なものにします。

resources/js/store.js

export default {
	state: {
		isLogin: false
	}
}

app.jsを下記のようにします。

resources/js/app.js

require('./bootstrap');

import Vue from 'vue';
import store from './store';
import router from './router';

window.state = store.state;

Vue.component('app', require('./components/App.vue'));

const app = new Vue({
	router
}).$mount('#app');

window.state = store.state;このようにすることで全てのファイルでstate変数が使用できるようになります。
routerApp.vueの部分は次で作成します。

ルーターの作成

ルーターはログインが必要なページはmetarequiresAuth: trueを指定します。
beforeEachでログインしていないとloginページにリダイレクトするように記述してます。

resources/js/router.js

import Vue from 'vue';
import VueRouter from 'vue-router';

import Home from './components/pages/Home'
import Login from './components/pages/Login'
import User from './components/pages/User'

Vue.use(VueRouter);

const routes = [
    { path: '/', component: Home },
    { path: '/login', component: Login },
    { path: '/user', component: User, meta: { requiresAuth: true } }
];

const router = new VueRouter({
    mode: 'history',
    routes
});

router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.requiresAuth)) {
        if (state.isLogin === false) {
            next({
                path: '/login',
                query: { redirect: to.fullPath }
            })
        } else {
            next()
        }
    } else {
        next();
    }
});

export default router;

App.vueコンポーネントの作成

最初にルーターの起点となるApp.vueを作成します。
ログアウトは機能だけなのでここに書いてます。

resources/js/App.vue

<template>
	<div>
		<ul>
			<li><router-link to="/">ホーム</router-link></li>
			<li><router-link to="/login">ログイン</router-link></li>
			<li><router-link to="/user">ユーザー情報</router-link></li>
			<li @click="logout">ログアウト</li>
		</ul>
		<hr>
		<router-view></router-view>
	</div>
</template>

<script>
	export default {
		methods: {
			logout() {
				axios.get('/api/logout').then(res => {
					state.isLogin = false;
					this.$router.push({path: '/'});
				});
			}
		}
	}
</script>

次からはルーターで設定した、各コンポーネントファイルを作成していきます。

Login.vueコンポーネントの作成

ログインページ用のコンポーネントです。

resources/js/components/pages/Login.vue

<template>
	<div>
		<p v-show="isError">認証に失敗しました。</p>
		<form @submit.prevent="login">
			<h1>ログイン</h1>
			メールアドレス: <input type="email" v-model="email">
			パスワード: <input type="password" v-model="password">
			<button type="submit" class="btn btn-primary">ログイン</button>
		</form>
	</div>
</template>

<script>
export default {
	data () {
		return {
			isError: false,
			email: '',
			password: '',
		}
	},
	methods: {
		login() {
			axios.post('/api/login', {
				email: this.email,
				password: this.password
			}).then(res => {
				const token = res.data.access_token;
				axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
				state.isLogin = true;
				this.$router.push({path: '/'});
			}).catch(error => {
				this.isError = true;
			});
		}
	}
}
</script>

ログインに成功したら、axiosAuthorizationにトークンを入れます。
これでログインが必要なAPIにアクセスができるようになります。

User.vueコンポーネントの作成

ログインした後ユーザー情報を取得して表示するためのコンポーネントです。

resources/js/components/pages/User.vue

<template>
	<div>
		<p v-show="isError">情報の取得に失敗しました。</p>
		<h1>ユーザー情報</h1>
		<table>
			<tr>
				<th>ID</th>
				<td>{{ user.id }}</td>
			</tr>
			<tr>
				<th>ユーザー名</th>
				<td>{{ user.name }}</td>
			</tr>
			<tr>
				<th>メール</th>
				<td>{{ user.email }}</td>
			</tr>
			<tr>
				<th>登録日</th>
				<td>{{ user.created_at }}</td>
			</tr>
		</table>
	</div>
</template>

<script>
	export default {
		data () {
			return {
				isError: false,
				user: {}
			}
		},
		created() {
			axios.get('/api/me').then(res => {
				this.user = res.data;
			}).catch(error => {
				this.isError = true;
			});
		}
	}
</script>

ざっくりではありますが、なんとなく動きがわかったかなということで以上にになります。