Laravel 5.4 と Vue.js 2.2 と JWTAuth で、ログインできる SPA アプリケーションのチュートリアル 1/4

Laravel 5.4 と Vue.js 2.2 と JWTAuth で、ログインできる SPA なTODOアプリを作ってみました。

結構本気で作りました。
Laravel + Vue.js はかなり作りやすく、サーバーサイドの処理が少ない＆分かりやすいので楽しかったです。

自分の記憶の定着を上げるため、チュートリアルを書きます。

JS初心者なので、おかしいところとかベストじゃないところが多々あると思いますので
ご指摘頂けたら嬉しいです！

# 作ったもの

ソースコード

https://github.com/acro5piano/laravel-vue-jwtauth-spa-todo-app

デモサイト

https://laravel-vue-jwtauth.herokuapp.com/

![1dfccb22-fdce-179d-8e85-f52a502662c2.png](https://qiita-image-store.s3.amazonaws.com/0/103885/7b1996b1-aaad-fdaf-38c6-26ba768605d8.png)

# 目次

四部作です。

1. はじめに       **← 今ここ**
  - 基本的な概念の説明
  - インストール
- [Todoアプリ作成編 / サーバーサイド](http://qiita.com/acro5piano/items/7ae04c0e57d0027f63c9)
  - Model作成
  - Restfulコントローラー
  - curlコマンドでテスト
  - Model Factory と Seeding
  - ユニットテストを書く
- [Todoアプリ作成編 / フロントエンド](http://qiita.com/acro5piano/items/a2c30bdd2b6f1a0d9c7d)
  - Vue インスタンスを作る
  - Vue component とルーティング
  - axios を使ってリクエストを飛ばす
- [JWTAuthでログイン編](http://qiita.com/acro5piano/items/eb29f13b82f386220460)
  - JWTAuth インストール
  - ログインとaxiosにヘッダー追加
  - Storeパターンを使ってログインState維持
  - ログアウト

一応部分的に読めるようにしていて、

- 概念だけ知りたい方は `1`
- Vue.jsをちょっと使いたい方は `3`
- JWTAuthの実装について知りたい方は `4`

だけで良いのかなと思います。

# 著者のスペック

- 本職はサーバーサイドエンジニア
- Rails, Laravel の使用経験があり、MVC, DBマイグレーション, RESTFul, ActiveRecord などの概念は一通り分かる
- JSはjQueryで止まってる
    - ES6なにそれ
    - React, Redux, Angular, Ember どれも使用経験無し
- Vue.jsの[公式ガイド](https://vuejs.org/v2/guide/)は一通り読んで、データバインディングについては理解した


# 参考にしたサイト

- [Koel](https://github.com/phanan/koel) 個人の音楽ストリーミングサービス。Laravel + Vue.js + JWTAuth で作られたSPAアプリケーション。一番 ~~パクった~~ 参考にした。
- [Vuedo](https://github.com/Vuedo/vuedo) Laravel + Vue.js のブログプラットフォーム。
- Laravel.io / Laracast / Stack Overflow での無数の Q&A


それでは始めます。

# 基本的な概念

実際に手を動かす前に、今回やりたいこと、学んだことを書いていきます。

### なぜ Single Page Application (SPA) にするのか

ユーザー体験が良いためです。

今回作って思いましたが、最初の読み込みさえ終わってしまえばサクサク動くSPAは気持ちいいですね。

モバイルとの相性も良いです。

### なぜ Laravel を使うのか

Laravelいいっすね。

- フロントエンドの処理を NodeJS (5.4では `Webpack` ）に任せているのでシンプル。
- 最初から Vue.js、 Webpack によるコンポーネント志向のアプリケーションが作れる
- 軽くて、依存関係などのトラブルが少ない（主観）
- `Model Factory` や `Faker`, `PHPUnit` など、開発支援がPHP随一で強い

Railsはフロントが難しい印象があります。 5.1 で変わったのかな・・・

### なぜ Vue.js を使うのか

Laravelと相性が良いためです。

Laravelを作った人がVue.jsを支援しているので、
Laravelをインストールすると、既にVue.jsもインストールされてます。

Vue.js はモダンなJavaScriptのフレームワークです。

アプリケーション全体の中で部分的に導入することもできますが、
豊富なエコシステムによりシンプルで軽量なSPAを構築することができます。

今回使ったのは下記です。

- vue-router
- vue-spinner
- axios

もっと大きなアプリケーションになれば、 `vuex` のような状態管理方法を使うと良いと思います。

### JWTAuth とは

シンプルな認証方式です。

[Introduction to JSON Web Tokens](https://jwt.io/introduction/) を読めば仕様は何となく理解できると思います。

かいつまんで説明すると

- クライアント: ユーザー名とパスワードをサーバーに送信
- サーバー:    ログインを試行し、成功したら `Json Web Token` を返す
- クライアント: Token を受取り、 
    - 次回以降 Request Header に `Authentication` を追加 ( `Authentication: Bearer "yowqeh43gb093fh023.....orhgoerg="` )
    - ローカルストレージ（かクッキー）に Token を保存し、Token の有効期限が切れるまでログイン状態維持
    - ログアウト時は明示的にTokenが破棄される

![](https://cdn.auth0.com/content/jwt/jwt-diagram.png)

[追記]

参考にしたプロジェクトではローカルストレージにTokenを保存していましたが、クッキーの方がセキュリティ上良いようです。

https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage

[/追記]

ちなみに、Laravel5.3からは Passport という機能が追加されており、
それを使えばAPIサーバーが簡単に構築できるらしいです。

ですが、下記のQ&Aによると

OAuth or JWT? Which one to use and why?
http://stackoverflow.com/questions/32964774/oauth-or-jwt-which-one-to-use-and-why

> If you want simple stateless http authentication to an api,
> then JWT is just fine and relatively quick to implement, even for a novice developer.

とのことなので、JWTAuthを使いました。

Passportは OAuth を利用しており、APIを外部に公開するならばぜひ使いたい機能ではあります。

### コンポーネント

Vue.js は `React` と同様、コンポーネント志向です。

例えば下記のような拡張子が `.vue` のファイルを作り、それを `Webpack` で結合してブラウザに渡します。


```html+jinja
<template>
  <h1>Hello, {{ name }}</h1>
</template>

<script>
  export default {
    data () {
      return {
        name: 'Kazuya'
      }
    },
  }
</script>

<style>
  .h1 {
    border-bottom: 1px;
  }
</style>
```

↓

```html
  <h1>Hello, Kazuya</h1>
```
jQuery 脳から進歩していない私にとって奇妙でしたが、すぐに慣れました。

このようなコンポーネントを組み合わせて、SPAを作っていきます。

# インストール

### PHP

いつもと同じ、 Laravel のインストール手順です。

```bash
composer create-project --prefer-dist laravel/laravel spa-todo 5.4
cd spa-todo
```

### Database

デモなので `sqlite` を使います。

```
# 初期設定で MySQL を使うような設定になっているが、 SQLite を使うため DB_ から始まる設定を削除
perl -i -pe 's/DB_.+\n//g' .env
echo 'DB_CONNECTION=sqlite' >> .env
touch database/database.sqlite

# DBのマイグレーションして動くことを確認
php artisan migrate

# => Migrated: 2014_10_12_000000_create_users_table
# => Migrated: 2014_10_12_100000_create_password_resets_table
```

### NodeJS

SPAに必要なモジュールを追加します。

関係ないですが、 `yarn` 速くて気持ちいいですね。

```bash
# お好みでyarn使おう
npm install -g yarn

# vue-router 追加
# npm install --save-dev vue-router でも一緒
yarn add vue-router vue-spinner --dev
```

package.json 修正

`bin/` の前に `dist` 付けないと動かなかった。この辺は環境に依りそう。

```rb:package.json
{
  ...,
  "scripts": {
    "dev": "node_modules/cross-env/dist/bin/cross-env.js ...",
    "watch": "node_modules/cross-env/dist/bin/cross-env.js ...",
    "hot": "node_modules/cross-env/dist/bin/cross-env.js ...",
    "production": "node_modules/cross-env/dist/bin/cross-env.js ...",
  },
  ...
}
```

※ 追記

@notice_inc さんに教えて頂き、下記のように修正した方がパスがすっきりしていいようです。
ありがとうございました。

```
yarn add cross-env --dev
```

```rb:package.json
{
  ...,
  "scripts": {
    "dev": "cross-env.js ...",
    "watch": "cross-env.js ...",
    "hot": "cross-env.js ...",
    "production": "cross-env.js ...",
  },
  ...
}
```

※さらに追記

@kimama1997 さんに、現在Gitリポジトリの方では修正されていることを教えて頂きました。
こちらの Mar 6, 2017 のコミットで修正されているので、そのうち修正されるはずです。

https://github.com/laravel/laravel/commit/65c4d16d04f030633b0b4a09c2c98f716e5c1873#diff-b9cfc7f2cdf78a7f4b91a753d10865a2


### Laravelのディレクトリ構成

一旦整理しておくと、

- フロントエンドは、 `resources/`
- サーバーサイドは、 `app/`
- ルーティングは 、 `routes/`

に置いていくことになります。

```
.
|-- package.json
|-- resources
|   |-- views
|   |   `-- app.blade.php
|   `-- assets
|       `-- js
|           `-- app.js
`-- routes
    `-- web.php
```

### Hello World

Laravel はアプリケーション開始時に単一Viewを返すだけで良いので、ルーティングは至極単純です。

ブラウザのルーティングはVue.jsが担当します。

```PHP:routes/web.php
<?php
Route::get('/{any}', function () {
    return view('app');
})->where('any', '.*');
```

SPAの起点となるViewを作ります。

`<div id="app"> ... </div>` の部分が Vue によって操作されます。

```html+django:resources/views/app.blade.php
<!DOCTYPE html>
<html lang="{{ config('app.locale') }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Vue TODO</title>

        <link rel="stylesheet" href="css/app.css">

        <script>
            window.Laravel = {};
            window.Laravel.csrfToken = "{{ csrf_token() }}";
        </script>
    </head>
    <body>
        <div id="app">
          <example></example>
        </div>
    </body>

    <script src="js/app.js"></script>
</html>
```

```js:resources/assets/js/app.js
require('./bootstrap');

Vue.component('example', require('./components/Example.vue'));

const app = new Vue({
    el: '#app'
});
```

### 起動

```
yarn run dev
php artisan serve
```

localhost:8000 で起動してるはず。

![localhost-8000-(iPhone 6).png](https://qiita-image-store.s3.amazonaws.com/0/103885/17aa0797-4569-ca6d-1d8b-b6c98d3b5598.png)

Vue.jsが表示できました。
このまま開発するなら、 `yarn run watch` を起動しておきましょう。変更を検知してJSをビルドしてくれます。

とりあえずここまで。