Laravelの認証(JWT)

# 目次
Laravelの記事一覧は下記
[PHPフレームワークLaravelの使い方](https://qiita.com/toontoon/items/c4d0371e504c37f6576e)

# Laravelバージョン
動作確認はLaravel Framework 7.19.1で行っています

# 前提条件
[eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)](https://qiita.com/toontoon/items/76fa7f8845b20776077c)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています

[Laravelでデータベースを扱う準備をする](https://qiita.com/toontoon/items/740b9575d2a323e93c0d)
[Laravelでテーブル作成](https://qiita.com/toontoon/items/2948a722a0ae7da2fb33)
[Laravelで初期データ投入](https://qiita.com/toontoon/items/f081f7f9f1667233480a)
[Laravelでデータベースを操作する(Eloquent編)](https://qiita.com/toontoon/items/97a49d1ea94e30bb8b48)
本記事は上記ので作成したデータベースとレコード、PHPファイルを使用します

# 認証に必要なソース生成
コマンドラインで 
`cd sample`
`composer require tymon/jwt-auth`
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します

コマンドラインで 

```
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
```
eclipseプロジェクトを右クリック→リフレッシュ
/sample/config/jwt.phpが現れます

コマンドラインで 
`php artisan jwt:secret`
eclipseプロジェクトを右クリック→リフレッシュ
/sample/.envにJWT_SECRETが記載されます

# Model修正
/sample/app/Models/User.php修正
(1) use文追記
`use Tymon\JWTAuth\Contracts\JWTSubject;`追記
(2) implementsにJWTSubject追加
`class User extends Authenticatable implements MustVerifyEmail , JWTSubject`
(3) メソッド追加
`    public function getJWTIdentifier()`
`    {`
`        return $this->getKey();`
`    }`
`    public function getJWTCustomClaims()`
`    {`
`        return [];`
`    }`

# auth.php修正
/sample/config/auth.php修正
guards->apiを修正

```auth.php
‥‥
    'guards' => [
        ‥‥
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],
‥‥
```

# Controller
(1) AuthController作成
コマンドラインで 
`cd sample`
`php artisan make:controller AuthController`
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
eclipseプロジェクトを右クリック→リフレッシュ
/sample/app/Http/Controllers/AuthController.phpが現れます
/sample/app/Http/Controllers/AuthController.phpを下記に修正します

```AuthController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AuthController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login']]);
    }

    public function login()
    {
        $credentials = request(['email', 'password']);

        if (! $token = auth('api')->attempt($credentials)) {
            return response()->json(['error' => 'メールアドレス、またはパスワードが間違っています'], 401);
        }

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

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

    public function logout()
    {
        auth('api')->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    public function refresh()
    {
        return $this->respondWithToken(auth('api')->refresh());
    }

    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60
        ]);
    }
}
```
[jwt-authの公式ドキュメント](https://jwt-auth.readthedocs.io/en/develop/quick-start/#create-the-authcontroller)とほとんど同じですが、違っている箇所があります。
auth()関数に引数'api'を渡しています
[jwt-authの公式ドキュメント](https://jwt-auth.readthedocs.io/en/develop/quick-start/#create-the-authcontroller)ではauth.phpのdefaultsのguardをapiにしているので、auth()に引数を渡さなくてもjwtのguardが取れますが、今回はdefaultsのguardはwebのままにしますので、auth()関数に引数'api'を渡すようにします
また、コンストラクタでmiddlewareを指定しています。middlewareは/sample/routes/web.phpや/sample/routes/api.phpの中でRoute::getやRoute::postの戻り値に対して指定することもできますが、コンストラクタで指定することもできます。この場合`$this->middleware('auth:api', ['except' => ['login']]);`なので、loginメソッドを除いて、メソッドにリクエストが来るとguard->apiの認証が必要になります

(2) SampleController.php修正
/sample/app/Http/Controllers/SampleController.php修正
api1メソッド、api2メソッド追加

```SampleController.php
    public function api1()
    {
        return view('sample.api');
    }

    public function api2(Request $request)
    {
        $data = [
            'a' => 'a_value',
            'b' => 'b_value',
            'c' => $request->input('c')
        ];
        return $data;
    }
```

(3) /sample/routes/api.php修正
下記を追記

```api.php
    Route::post('auth/login', 'AuthController@login');
    Route::post('auth/logout', 'AuthController@logout');
    Route::post('auth/refresh', 'AuthController@refresh');
    Route::post('auth/me', 'AuthController@me');
    Route::post('sample/api2', 'SampleController@api2')->middleware('auth:api');
```
/sample/routes/api.phpにルーティングを書いた場合、app/Providers/RouteServiceProvider.php#mapApiRoutesによって自動的に/apiがURLの先頭につきます
AuthControllerの各メソッドはAuthControllerのコンストラクタ内で認証の要、不要が設定されていますが、SampleControllerのapi2はここで認証が必要なようにmiddlewareを付けました

(4) /sample/routes/web.php修正
下記を追記
`Route::get('sample/api1', 'SampleController@api1');`

# 要ログインURLへ未認証ユーザーがアクセスした場合のエラーメッセージ修正
/sample/app/Http/Middleware/Authenticate.php修正
unauthenticatedメソッド追加

```
    protected function unauthenticated($request, array $guards)
    {
        throw new AuthenticationException(
            'ログインしてください', $guards, $this->redirectTo($request)
            );
    }
```

# view作成
/sample/resources/views/sample/api.blade.php

```api.blade.php
<html>
    <head>
        <title>sample</title>
        <script type="text/javascript">

            function login(){

            	var form = document.getElementById("sampleForm1");

            	var reqData = {"email": null, "password": null};
            	reqData.email = form.email.value;
            	reqData.password = form.password.value;

            	var req = new XMLHttpRequest();
            	req.open(form.method, form.action);
            	req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
            	req.responseType = 'json';
            	req.send(JSON.stringify(reqData));
            	req.onload = function () {
            		var json = req.response;
            		if (json['error']) {
                		alert("error:" + json['error']);
            		} else {
            			localStorage.setItem('access_token', json['access_token']);
                		alert('ログイン');
            		}
            	}
            }
            function ajax(){

            	var form = document.getElementById("sampleForm2");

            	var reqData = {"c": null};
            	reqData.c = form.c.value;

            	var req = new XMLHttpRequest();
            	req.open(form.method, form.action);
            	req.setRequestHeader('Authorization', 'Bearer ' + localStorage.getItem('access_token'));
            	req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            	req.setRequestHeader('Accept', 'application/json');
            	req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
            	req.responseType = 'json';
            	req.send(JSON.stringify(reqData));
            	req.onload = function () {
            		var json = req.response;
            		if (json['message']) {
                		alert("message:" + json['message']);
            		} else {
                		alert("a:" + json['a']  + "\n" + "b:" + json['b']+ "\n" + "c:" + json['c']);
            		}
            	}
            }
            function logout(){

            	var form = document.getElementById("sampleForm3");

            	var req = new XMLHttpRequest();
            	req.open(form.method, form.action);
            	req.setRequestHeader('Authorization', 'Bearer ' + localStorage.getItem('access_token'));
            	req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            	req.setRequestHeader('Accept', 'application/json');
            	req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
            	req.responseType = 'json';
            	req.send();
            	req.onload = function () {
            		var json = req.response;
            		alert("message:" + json['message']);
            	}
            }
        </script>
    </head>
    <body>

        <form id="sampleForm1" action="{{ url('api/auth/login') }}" method="post"  onsubmit="login(); return false;" style="border: #000000 solid 1px; padding: 10px;">
            <div>email:<input type="text" name="email" ></div>
            <div>password:<input type="password" name="password" ></div>
            <input type="submit" value="ログイン">
        </form>

        <br>
        <br>

        <form id="sampleForm2" action="{{ url('api/sample/api2') }}" method="post"  onsubmit="ajax(); return false;" style="border: #000000 solid 1px; padding: 10px;">
            <input type="text" name="c" >
            <input type="submit" value="要認証リクエスト">
        </form>

        <br>
        <br>

        <form id="sampleForm3" action="{{ url('api/auth/logout') }}" method="post"  onsubmit="logout(); return false;" style="border: #000000 solid 1px; padding: 10px;">
            <input type="submit" value="ログアウト">
        </form>

    </body>
</html>
```

# 動作確認

`http://localhost/laravelSample/sample/api1`
![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238103/18e77d4b-b67f-b4cd-9d32-56ab1f86007d.png)
・要認証リクエストの左にあるテキストフィールドに何か入力し、要認証リクエストボタンをクリックします
ログインしてくださいとalertがでました
・メールアドレスとパスワードを入力してログインボタンをクリックします
ログインできました
・要認証リクエストの左にあるテキストフィールドに何か入力し、要認証リクエストボタンをクリックします
SampleController.php#api2のレスポンスが表示されました
・ログアウトボタンをクリックします
ログアウトしました