Laravel5.5こと始め 〜10. APIへのJWTAuth認証の追加〜

# 内容
以下の順番にまとめます。
[1. MacへのXAMPP+Laravelインストール](https://qiita.com/sonrisa/items/f08112fe0ec4f51e4bdf)
[2. ユーザログイン機能の追加](https://qiita.com/sonrisa/items/207fd53eced13fc88acf)
[3. MVCとルーティングの説明]
(https://qiita.com/sonrisa/items/809adf872e6fb3657f81)
[4. ユーザリストの表示]
(https://qiita.com/sonrisa/items/929486929ed8d851a24e)
[5. ユーザリストのペジネーション表示]
(https://qiita.com/sonrisa/items/86efb8caebe207ab8fa8)
[6. ユーザ管理APIの追加](https://qiita.com/sonrisa/items/bc4894e6803632089ceb)
[7. Vue.jsとAPIベースのユーザ管理アプリの追加準備](https://qiita.com/sonrisa/items/595f080c6ecf1ed61b93)
[8. Vue.jsとAPIベースのユーザ管理アプリの追加](https://qiita.com/sonrisa/items/48a9a8e73e757c81619a)
[9. Vue.jsとAPIベースのユーザ管理アプリへのペジネーション追加]
(https://qiita.com/sonrisa/items/d58c56b638924d8c6810)
[10. APIへのJWTAuth認証の追加]
(https://qiita.com/sonrisa/items/97c1f3e8491f76dc2ce2) ←いまここ
[11. Vue.jsとAPIベースのユーザ管理アプリへの認証の追加](https://qiita.com/sonrisa/items/1976cbd049cadfec1285)

# 10. APIへのJWTAuth認証の追加

home2へのアクセス時にログインを求められますが、APIには認証の実装がなされておらずAPIアクセスは誰でもできてしまいますので、このままでは問題です。そのため、home2へのアクセスは誰でもできるようにし、APIアクセスにJWTAuthを使って認証の機能を実装します。その後、次ページでVue.jsで実装されたSPA画面上でログインを実装することとします。

## 10.1 URI「home2」の認証を削除

認証はルーティング部分で設定していますので、「routes/web.php」を編集し、home2に認証なしでアクセスできるようにします。

```routes/web.php
...(略)
//Route::middleware('auth')->get('/home2', function () {
//    return view('home2');
//})->name('home2');

Route::get('/home2', function () {
    return view('home2');
});
```

「php artisan serve」を実行して、「http://localhost:8000/home2 」にアクセスし、ログアウトしてもアクセスできることを確認します。

<img width="704" alt="スクリーンショット 2018-07-16 23.20.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/263562/22dbd4b7-572e-dc39-6e6f-99b3820c47cc.png">

## 10.2 APIへのJWTAuth認証の追加

### 1. JWTAuthのインストール
Composerコマンドを実行してJWTAuthをインストールします。

```
$ composer require tymon/jwt-auth
Using version ^0.5.12 for tymon/jwt-auth
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 4 installs, 0 updates, 0 removals
  - Installing symfony/polyfill-util (v1.8.0): Loading from cache
  - Installing symfony/polyfill-php56 (v1.8.0): Loading from cache
  - Installing namshi/jose (7.2.3): Loading from cache
  - Installing tymon/jwt-auth (0.5.12): Loading from cache
namshi/jose suggests installing phpseclib/phpseclib (Allows to use Phpseclib as crypto engine, use version ^2.0.)
Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
```

### 2. 「config/app.php」への設定

下記の通り、JWTAuthのprovidersとaliasesを設定します。

```config/app.php
... 中略
    'providers' => [
... 中略
        Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
    ],
... 中略
    'aliases' => [
... 中略
        'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
        'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
    ],
];
```

### 3. 「app/Http/Kernel.php」への設定

下記の通り、ルーティングでJWTAuthが使えるように設定します。

```app/Http/Kernel.php
... 中略
    protected $routeMiddleware = [
	'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
	'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
	'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
	'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
	'can' => \Illuminate\Auth\Middleware\Authorize::class,
	'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
	'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
	'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,

	'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,	// 追加
	'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class, // 追加
    ];
}
```

### 4. JWTAuthの設定ファイル作成

以下のコマンドを実行して「config/jwt.php」ファイルを作成します。

```
$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"
Copied File [/vendor/tymon/jwt-auth/src/config/config.php] To [/config/jwt.php]
Publishing complete.
```

### 5. 秘密鍵の作成

以下のコマンドを実行して秘密鍵の生成を試みます。

```
$ php artisan jwt:generate

   ReflectionException  : Method Tymon\JWTAuth\Commands\JWTGenerateCommand::handle() does not exist

  at /Users/Retina/userauth/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:135
    131|             $callback = explode('::', $callback);
    132|         }
    133| 
    134|         return is_array($callback)
  > 135|                         ? new ReflectionMethod($callback[0], $callback[1])
    136|                         : new ReflectionFunction($callback);
    137|     }
    138| 
    139|     /**

  Exception trace:

  1   ReflectionMethod::__construct(Object(Tymon\JWTAuth\Commands\JWTGenerateCommand), "handle")
      /Users/Retina/userauth/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:135

  2   Illuminate\Container\BoundMethod::getCallReflector()
      /Users/Retina/userauth/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:115

  Please use the argument -v to see more details.
```

「JWTGenerateCommand.php」にhandle関数がないためエラーが発生しますので、以下の通りhandle関数を追加します。

```vendor/tymon/jwt-auth/src/Commands/JWTGenerateCommand.php
... 中略
    public function handle() {
        $this->fire();
    }
}
```

再度秘密鍵の生成を試みます。

```
$ php artisan jwt:generate
jwt-auth secret [thQFW5fwIfpQiHSKkZfjblb0A9TP8dUP] set successfully
```

「config/jwt.php」ファイルに秘密鍵の設定が追加されていることを確認します。

```
$ grep secret config/jwt.php 
    'secret' => env('JWT_SECRET', 'thQFW5fwIfpQiHSKkZfjblb0A9TP8dUP'),
```



### 6. 認証関連のAPIの実装

ここまでに「UserController.php」で定義されたAPIはルーティング「routes/api.php」での定義も含め、全く認証の設定がなされていませんでした。そのため、以下の3点の設定を行います。

1) 認証キー発行のAPI実装（これは認証なしでアクセス可能）
2) ログインしているユーザの情報を返すAPI実装（要認証）
3) 既存ユーザー情報管理APIは認証がないとアクセスができないよう設定

これらは、「UserController.php」へのAPI実装と「routes/api.php」へのルーティング設定で行います。

#### 1) 「UserController.php」へのAPI実装

```app/Http/Controllers/UserController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\User;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;

class UserController extends Controller
{
... 中略
    public function authenticate(Request $request) // emailとpasswordでアクセストークンを取得して返します
    {
        $credentials = $request->only('email', 'password');
        try {
            if (! $token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 401);
            }
        } catch (JWTException $e) {
            return response()->json(['error' => 'could_not_create_token'], 500);
        }
        $user = User::where('email', $request->email)->first();
        return response()->json(compact('user', 'token'));
    }

    public function getCurrentUser() // トークンに紐づくユーザ情報を取得して返します
    {
        $user = JWTAuth::parseToken()->authenticate();
        return response()->json(compact('user'));
    }
}
```

これにより、「1) 認証キー発行のAPI実装（これは認証なしでアクセス可能）」と「2) ログインしているユーザの情報を返すAPI実装（認証なし）」が実装されます。次にルーティングと認証の設定を追加します。

#### 2) 「routes/api.php」へのルーティング設定

```routes/api.php
... 中略
Route::post('authenticate','UserController@authenticate');
Route::group(['middleware' => 'jwt.auth'], function () {
  Route::resource('users', 'UserController');
  Route::get('me',  'UserController@getCurrentUser');
});
```

これにより、「認証キー発行のAPI」と「既存ユーザー情報管理API」に認証が必要となり、ルーティングの設定が完了します。

### 7. JWTAuth認証のテスト

例のごとく、「php artisan serve」を実行して、APIのテストを行います。

#### 1) アクセストークンの取得

```
$ curl -X POST localhost:8000/api/authenticate -d 'email=test1@test.com' -d 'password=(uhBnji9' | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   433    0   395  100    38   2392    230 --:--:-- --:--:-- --:--:--  2393
{
  "user": {
    "id": 1,
    "name": "test1",
    "email": "test1@test.com",
    "created_at": "2018-07-18 21:41:12",
    "updated_at": "2018-07-18 21:41:12"
  },
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9hcGkvYXV0aGVudGljYXRlIiwiaWF0IjoxNTMxOTg4ODg5LCJleHAiOjE1MzE5OTI0ODksIm5iZiI6MTUzMTk4ODg4OSwianRpIjoiYm5FQlBXNVBSanBEZ0FrYSJ9.Q9ytQ-sLFsg92d5GcPytIKlFJuVA55vQBaAo1LqayNo"
}
```

ここでいう、"token"の値がアクセストークンになります。

#### 2) APIの実行

アクセストークンなしでAPIを実行すると「token_not_provided」と言われエラーになります。

```
$ curl -X GET localhost:8000/api/users | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    30    0    30    0     0    413      0 --:--:-- --:--:-- --:--:--   416
{
  "error": "token_not_provided"
}
```

取得したアクセストークンをセットしてAPIを実行するとうまくいきます。

以下、ユーザー情報取得するAPIの実行です。

```
$ curl -X GET localhost:8000/api/users  -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9hcGkvYXV0aGVudGljYXRlIiwiaWF0IjoxNTMxOTg4ODg5LCJleHAiOjE1MzE5OTI0ODksIm5iZiI6MTUzMTk4ODg4OSwianRpIjoiYm5FQlBXNVBSanBEZ0FrYSJ9.Q9ytQ-sLFsg92d5GcPytIKlFJuVA55vQBaAo1LqayNo'| jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   449    0   449    0     0   1915      0 --:--:-- --:--:-- --:--:--  1910
{
  "current_page": 1,
  "data": [
    {
      "id": 1,
      "name": "test1",
      "email": "test1@test.com",
      "created_at": "2018-07-18 21:41:12",
      "updated_at": "2018-07-18 21:41:12"
    }
  ],
  "first_page_url": "http://localhost:8000/api/users?page=1",
  "from": 1,
  "last_page": 4,
  "last_page_url": "http://localhost:8000/api/users?page=4",
  "next_page_url": "http://localhost:8000/api/users?page=2",
  "path": "http://localhost:8000/api/users",
  "per_page": 1,
  "prev_page_url": null,
  "to": 1,
  "total": 4
}
```

以下、アクセストークンに対応するユーザ情報を取得するAPIの実行です。

```
$ curl -X GET localhost:8000/api/me  -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9hcGkvYXV0aGVudGljYXRlIiwiaWF0IjoxNTMxOTg4ODg5LCJleHAiOjE1MzE5OTI0ODksIm5iZiI6MTUzMTk4ODg4OSwianRpIjoiYm5FQlBXNVBSanBEZ0FrYSJ9.Q9ytQ-sLFsg92d5GcPytIKlFJuVA55vQBaAo1LqayNo'| jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   127    0   127    0     0    440      0 --:--:-- --:--:-- --:--:--   440
{
  "user": {
    "id": 1,
    "name": "test1",
    "email": "test1@test.com",
    "created_at": "2018-07-18 21:41:12",
    "updated_at": "2018-07-18 21:41:12"
  }
}
```

以上、「10. APIへのJWTAuth認証の追加」の完了です。

次は、「[11. Vue.jsとAPIベースのユーザ管理アプリへの認証の追加](https://qiita.com/sonrisa/items/1976cbd049cadfec1285)」です。

# 参考URL

参考1. JWTAuthのインストール方法　http://jwt-auth.readthedocs.io/en/develop/laravel-installation/#add-service-provider-laravel-54-or-below
参考2. 秘密鍵生成「php artisan jwt:generate」が失敗しないようにするためのJWTGenerateCommand.phpの編集 https://laracasts.com/discuss/channels/laravel/class-tymonjwtauthprovidersjwtauthserviceprovider-not-found-1