Laravel8だよ！全員集合！～認証！認可！大作戦！！の巻～

自分で書いといてなんだけど、こんなタイトルの記事って胡散臭さぷんぷんだよね。ふざけているのか、こいつって思う。いや、ふざけてないよ。見捨てないで。

それはさておき、おいらの記事の中では、何故か一番人気の認証処理。なぜ？どうして？
人気の秘密はよくわからないけど、求められているなら今回も頑張るよ。
[環境構築したばかりのLaravelプロジェクト](https://qiita.com/apricotcomic/items/79432ca7f2703c3704a9)に、認証処理を追加してみます。
一応環境書いとくね。
Windows10
Vagrant2.2.10
Laravel Ver8

# Laravel Jetstreamをインストールする

Laravel Ver8では、Laravel uiは非推奨になって、代わりに[Laravel Jetstream](https://readouble.com/laravel/8.x/ja/authentication.html)を使うらしい。
では、言われるがままにやってみようじゃないか。

なになに、「真新しいアプリケーションを開始し、認証のスカフォールドを含めたい場合は、アプリケーションの生成時にLaravelインストーラへ--jetディレクティブを使ってください。」だと？
ちょっと待ったぁー！なにこれ、Laravelプロジェクトをインストールするときに設定するだと？聞いてねーよ、もう作っちまったよ、プロジェクト。
今度やるときは--jetつけてインストールしよ。こんな感じで。

```
> laravel new lara8test --jet
```

じゃあもうリカバリ不可か、というとそういう訳でない。
composerでインストールすることができるそうだ。
インストールはlaravelプロジェクトのフォルダに移動してから実行してね。

```
> composer require laravel/jetstream
```

で？livewireとInertiaって、何者だ？
Livewire→凄腕、元気者
Inertia→慣性、惰性
いや、直訳してもよくわかんね。
すごくざっくりいうと、LivewireはbladeでInertiaはVue.jsてことか？
まだSPAとかちょっと面倒だし、ここは慣れ親しんだblade系でチャレンジだ。
やっぱ元気ゾーンで行くっきゃないっしょ！

# 元気者で行こう！

元気者はartisanコマンドでインストールする。

```
> php artisan jetstream:install livewire
```

そうすると、プロジェクトのcomposer.jsonにjetstreamが追加されていたり、database/migrationsに何やら追加されていたりする。
では、migrationしてみましょう。migrationはvagrant upでhomesteadを立ち上げた後、プロジェクトのフォルダで実行してね。

```
~/code/lara8test$ php artisan migrate
```

migrate出来たら、laravelのサイトを立ち上げてみよう。
なんと右上にloginとregisterが追加されてるよ。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/4ef04fda-b860-54b9-5199-524e32f6df0c.png)|
|:-:|

loginをクリックすると、ログイン画面が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/38dd7d60-48f0-b850-1bfa-1950d663afce.png)

今はまだユーザー登録できていないから、前の画面に戻ってRegisterをクリックしてみよう。今度はユーザーの登録画面が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/4046f87d-9e0a-8cf4-269c-bc008a3a7191.png)

ユーザー登録してみよう。必要項目を入力して、REGISTERをクリックしよう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/0214273c-572d-16ff-b7fe-c60a5c8ca767.png)

そうすると、ユーザーが登録されてログインされる。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/40c588cd-dea5-14ea-8840-109cbf4f537f.png)|
|:-:|

おー、こっちもなんかデザイン変わってる。
右上のユーザー名をクリックすると、ユーザー情報の変更とログオフができる。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/acdb2124-f053-57a0-7694-5aa9b3cfd21c.png)|
|:-:|

もう出来たよ。相変わらず簡単だね。

# 大枠の流れを作ってしまう

本当はここから認証周りのカスタマイズをするのが記事としては正しい気がするが、気持ち的に自分のやりたい動きを先に作ってしまいたくなる。
自分の気持ちに正直に生きることにして、全体の流れを作ってしまう。
・Login後のリダイレクト先をメニュー画面にする。
・メニューから照会画面に遷移する。
　ログインしないで照会画面を開こうとしても、ログインしていないから開けないようにする。

…って、どこかで同じようなこと書いた気が？デジャヴー？
まあ、人の気持ちなんてそう簡単に変わらないもんさ。
それでは例によって適当なメニューを作ろう。

## メニュー画面を作る

おいらもあの頃より成長したよ。ぞんざいなメニューでも、以下の物を作らないといけないよね。もうネットには騙されないぞ。
・コントローラー→MenuController.phpを作るよ
・画面(Blade）→menu.blade.phpを作るよ
・ルートの追加→route/web.phpにMenuController.phpを追加するよ

### MenuController.phpを作ろう

例によってartisan makeで作成するよ。プロジェクトを作成したフォルダに移動してからコマンド投入してね。

```
> php artisan make:controller Menu/MenuController
```

successfulが表示されればOK.プロジェクトのapp/Http/Controllerの下にMenuフォルダが作成され、その中にMenuController.phpが作られるよ。

```php:MenuController.php
<?php

namespace App\Http\Controllers\Menu;

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

class MenuController extends Controller
{
    //
}
```

ここにメニュー画面を表示する処理を追加する。

```php:MenuController.php
class MenuController extends Controller
{
    //
    public function menu()
    {
        return view('menu/menu');
    }
}
```

### ルーティングを設定する

メニューを表示する処理をroute/web.phpに追加する。
ルーティングの書き方はVer8から変わったんだって。
useでcontrollerを追加、Routeの書き方もちょっと違うよ。

```php:web.php
use App\Http\Controllers\Menu\MenuController;
～
Route::get('menu/menu', [MenuController::class, 'menu'])->name('menu');
```

### 画面を作る

resources/views/menu/menu.blade.phpを追加して、表示するメニュー画面を作成する。
menuフォルダは追加してね。
とりあえず内容はぞんざいな感じで作ってみる。

```php:menu.blade.php
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <a href="">照会処理</a><br>
        <a href="">更新処理</a>
    </body>
</html>
```

一旦のこの状態で、ブラウザから直接アクセスしてみる。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/cd92dcaf-eb59-382b-bd77-fc835887e788.png)|
|:-:|

ぞんざいな画面が表示されたよ。

## ログインしたらメニュー画面を表示するようにする

Ver8では自力で処理を書かなくても、ログイン後に遷移する画面を指定できるらしい。
/app/Providers/RouteServiceProvider.phpで設定するんだって。

```php:RouteServiceProvider.php
/**
     * The path to the "home" route for your application.
     *
     * This is used by Laravel authentication to redirect users after login.
     *
     * @var string
     */
    public const HOME = '/dashboard';
```

laravel標準はHOME = '/home'になっているらしいが、元気者Livewireをインストールするとdashboardに変わるらしい。
こいつを、自分で作成した画面の遷移先に変更する。

```php:RouteServiceProvider.php
/**
     * The path to the "home" route for your application.
     *
     * This is used by Laravel authentication to redirect users after login.
     *
     * @var string
     */
    public const HOME = '/menu/menu';
```

では、試してみましょう。
lara8.testでtop画面にアクセス。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/4dfb4002-4147-4dfa-d49f-caffed357e4d.png)|
|:-:|

ログインをクリックして、登録したユーザーでログインする。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/519d416b-3226-9147-3c05-cf9fb7995769.png)|
|:-:|

ログインボタンをクリックすると…

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/be1a5dbf-e9f4-e28f-97af-7b86de5ba754.png)|
|:-:|

おお、ぞんざいなメニュー画面が表示されたよ。これは簡単だね。
しかーし！！
このままでは、ログインしていなくても/menu/menuにアクセスできてしまう。前とおんなじだよ。
なので、MenuControllerに認証処理入れるよ。

# 認証してみる

チェックは今までと変わらず、Facades/Authのcheckメソッドでログイン済みかのチェックはできます。

```php:MenuController.php
<?php

namespace App\Http\Controllers\Menu;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;

class MenuController extends Controller
{
    //
    public function menu()
    {
        if (Auth::check()) {
            // ログイン済みだったらメニューを表示
            return view('menu/menu');
        } else {
            // ログインしていなかったらログイン画面を表示
            return view('auth/login');
        }
    }
}
```

# ログアウトできるようにする

このままだとメニュー画面からログアウトできないので、既存の処理をパクってログオフできるようにする。
ログアウトはresource/views/navigation-dropdown.blade.phpの中で行っている。

```php:navigation-dropdown.blade.php
～
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
    @csrf
    <x-jet-responsive-nav-link href="{{ route('logout') }}"
                  onclick="event.preventDefault();
                              this.closest('form').submit();">
                        {{ __('Logout') }}
    </x-jet-responsive-nav-link>
</form>
～
```

これをそのままコピーして、menu.blade.phpの中に入れてしまう。

```php:menu.blade.php
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <a href="">照会処理</a><br>
        <a href="">更新処理</a>
        // ここから丸パクリ
        <form method="POST" action="{{ route('logout') }}">
            @csrf
            <x-jet-dropdown-link href="{{ route('logout') }}"
                    onclick="event.preventDefault();
                        this.closest('form').submit();">
                {{ __('Logout') }}
            </x-jet-dropdown-link>
        </form>
       // ここまで丸パクリ
    </body>
</html>
```

ブラウザで見ると、こんな感じ。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/22b590c4-a8e2-8d1f-9aef-62232b36e909.png)|
|:-:|

Logoutをクリックすると、laravelのTOP画面に戻る。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/f0a22693-59d5-81eb-429b-2229bded0e3a.png)|
|:-:|

# 認可してみる

認証はざっくり言えばいわゆるログインのことだけど、ログイン後に許可する処理と許可しない処理を分けることを認可という。と思う。
今回のメニューで、照会は誰でもしていいけど更新は一部の人だけ、なんて制御をするときに使うのだ。
これを実現するのに、こんなことをやるよ。
・usersテーブルに項目を追加する。
・ぞんざいな参照画面、更新画面などを作る。
・MenuControllerに認可処理を追加する。

## usersテーブルに項目を追加する。

今回はありがちなアクセス権限を管理する項目を追加するよ。内容はこんな感じだ。
0:担当者→照会しかできない
1:責任者→照会・更新ができる
9:管理者→照会・更新に合わせ秘密が見れる

項目の追加は項目追加用のmigrationファイルを追加して行うことにする。
まずはマイグレーションファイルを作成するよ。プロジェクトのフォルダーに移動してコマンド打ってね。

```
> php artisan make:migration add_columns_users_table --table=users
```

追加したadd_columns_users_tableに、今回追加する項目「access_auth」を追加したソースがこちら。

```php:add_columns_users_table
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddColumnsUesrsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            // access_authを追加
            $table->string('access_auth', 1)->after('current_team_id');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            // access_authを追加
            $table->dropcolumn('access_auth');
        });
    }
}
```

追加した項目をテーブルに書き込むには、以下のソースも修正が必要だよ。
・/app/Actions/Fortify/CreateNewUser.php
→入力されたaccess_authをusersテーブルに設定する。
・/app/Models/User.php
→access_authを追加する。
・/app/resources/views/auth/register.blade.php
→register画面にacces_authを追加する。

修正イメージは下記参照してね。

```php:CreateNewUser.php
public function create(array $input)
    {
        Validator::make($input, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => $this->passwordRules(),
        ])->validate();
        // 入力された値をコード値に変換する処理を追加
        switch ($input['access_auth']) {
            case "User":
                $auth_code = "0";
                break;
            case "Manager":
                $auth_code = "1";
                break;
            case "Admin":
                $auth_code = "9";
                break;
        }
        // ここまで変換
        return User::create([
            'name' => $input['name'],
            'email' => $input['email'],
            'password' => Hash::make($input['password']),
            'access_auth' => $auth_code,　// テーブルへ書き込む処理を追加
        ]);
    }
```

```php:register.blade.php
protected $fillable = [
        'name',
        'email',
        'password',
        'access_auth',   // この項目を追加
    ];
```

```php:register.blade.php
// password_confirmationの下に追加
<div class="mt-4">
     <x-jet-label for="access_auth" value="{{ __('Access Auth') }}" />
     <select id="access_auth" class="block mt-1 w-full" type="access_auth" name="access_auth" :value="old('access_auth')" required >
          <option>User</option>
          <option>Manager</option>
          <option>Admin</option>
     </select>
</div>
```

本当は登録後のユーザー情報を修正するProfileも修正しないと、登録後アクセス権限変更できなくなっちゃうんだけど、面倒だから今はしない。気になる人は自力でレッツトライだ。

ソースの修正が終わったら、migrate:refreshでusersテーブルを作り直す。
homesteadの環境の中から実行してね。

```
$ php artisan migrate:refresh
```

ここまで出来たらブラウザでサイトにアクセスして、TOP画面から「Register」をクリックすると、access_authが追加されているよ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/b9defb3d-2d5a-e2d3-d0c3-d7dfc75ded06.png)

cssちゃんと当たってないからだっせぇ画面になっちゃったけど。windtailはいずれちゃんと向き合うよ。

とりあえずこんな感じで入力して登録する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/32da50ac-2725-0468-da5d-ec422390f6d8.png)

## ぞんざいな参照画面、更新画面などを作る

真剣に作る気はさらさらないが、入力画面用にcontrollerを作ってしまう。
こんなイメージで作るよ。
・TestinputController
→とりあえずartisan make:Controllerでcontrollerを作る。showとeditだけ画面遷移するよう変更する。
・showとeditだけ画面を作る。
→面倒だから、これしかしない。
・Adminだけが見れる秘密の画面は別に作る。
→面倒だからControllerはMenuControllerに混ぜる。

出来上がりのイメージは、このようになっています（料理番組風）。

```php:TestinputController.php
// 変更したところだけ
～
use Illuminate\Support\Facades\Gate;
～
    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //ぞんざいな照会画面を表示する
        return view('testinput.show');

    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        if(Gate::denies('user')) {
            //ぞんざいな更新画面を表示する
            return view('testinput.edit');
        } else {
            session()->flash('editmsg', 'あんた更新できないよ！！');
            return view('menu/menu');
        }
    }
```

GateはIlluminate\Auth\Access\GateでなくてIlluminate\Support\Facades\Gateを指定してください。
Illuminate\Auth\Access\Gateのままだと「staticでねーよ」というようなエラーが出ますよ。

```php:MenuController.php
// 以下の処理を追加
public function secret()
{
    return view('secret/secret');
}
```

```php:show.blade.php
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <h1>照会画面</h1>
        なんでも見せるよ！何が見たい？<br>
        <input id=input>
        <br><br>
        <button onClick="history.back()">{{ __('back') }}</button>
        <br>
    </body>
</html>
```

```php:edit.blade.php
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <h1>更新画面</h1>
        やばい値は書き換えるよ。何を変えたい？<br>
        項目
        <input type="text" name="inputdata">
        <br><br>
        <button type="submit" name="action" value="update">{{__('update')}}</button>
        <button onClick="history.back()">{{ __('back') }}</button>
        <br>
    </body>
</html>
```

resource/viewsにsecretフォルダを追加して、secret.blade.phpを追加する。

```php:secret.blade.php
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <h1>秘密</h1>
        ほら、秘密が見れた
        <br><br>
        <button onClick="history.back()">{{ __('back') }}</button>
    </body>
</html>
```

ついでに、テストしたときどのユーザーでアクセスしてるかわかるように、menu.blade.phpにユーザー名を表示する処理を追加する。

```php:menu.blade.php
<body>
    こんにちは！{{ Auth::user()->name }}<br><br>
```

## MenuControllerにGateを追加する

ここまで出来たら、いよいよ認可処理を組み込むよ。
認可の方法は、今までと同様GateとPolicyがあるよ。
例によってざっくりなので、Gateで設定してみるよ。

Gateの設定は、app\Providers\AuthServiceProvider.phpの中に設定する。

```php:AuthServiceProvider.php
public function boot()
{
    $this->registerPolicies();
    // ここから追加
    Gate::define('user', function ($user) {
        return $user->access_auth == '0';
    });
    Gate::define('manager', function ($user) {
        return $user->access_auth == '1';
    });
    Gate::define('admin', function ($user) {
        return $user->access_auth == '9';
    });
}
```

## 認可の仕組みを仕込む

Gateの設定は、要はよくあるアクセス権限の設定をしているだけです。
その権限で何ができるかは、いろんなところに仕込んで実現する。
実は、すでに仕込まれているものが…

### 認可されていない処理を行おうとするとき、エラーにする方法

さっきTestinputController.phpに追加した処理に、実は認可の仕組みが入っているのだよ。

```php:TestinputController.php
public function edit($id)
{
    if(Gate::denies('user')) {  ←これ
        //ぞんざいな更新画面を表示する
        return view('testinput.edit');
    } else {
        session()->flash('editmsg', 'あんた更新できないよ！！');
        return view('menu/menu');
    }
}
```

設定した権限でその処理を行うことができる/できないの設定は、Gateで制御する。Gate::allowsだと指定された権限でアクセスが可能な場合、Gate::deniesだと指定された権限でアクセスできない場合になるよ。
今回はadmin,manager,userのみっつの権限を設定してあって、adminとmanagerは更新処理を実行可能にしたかったので、逆の言い方でuserはアクセス不可、という設定にしてみました。
試すとこうなる。

管理ユーザーでログインしたとき

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/9bebfce8-4375-e7f5-1577-3f8be4eb17ab.png)|
|:-:|

更新処理をクリックすると、更新画面が表示される。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/08d5dc21-4c9e-8e54-ad33-e8808b79de71.png)|
|:-:|

一般ユーザーでログインしたとき

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/97b5e3d7-51a1-507c-4867-2575279c0446.png)|
|:-:|

更新処理をクリックすると、エラーメッセージが表示される。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/c142417e-2cfa-669d-8345-bf7bd4619828.png)|
|:-:|

if文で判定できるから、画面遷移の時だけでなく、いろんなパターンで認可のチェックができそうだね。

一方、今回みたいにメニューが出ているのにクリックしたらエラーなんて、詐欺もいいところだよね！そもそもこんなの出すなよ！って思うよね。
そういう時は、bladeの方で設定できる。

```php:menu.blade.php
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        こんにちは！{{ Auth::user()->name }}<br><br>
        <a href="{{ route('testinput.show' ,'id') }}">照会処理</a><br>
        <a href="{{ route('testinput.edit', 'id') }}">更新処理</a>
        @if(session('editmsg'))
            {{ Session('editmsg') }}
        @endif
        <br>
        @can('admin') ←これ
            <a href={{ route('secret') }}>秘密見せるよ</a>
        @endcan
        <form method="POST" action="{{ route('logout') }}">
            @csrf
            <x-jet-dropdown-link href="{{ route('logout') }}"
                                onclick="event.preventDefault();
                                            this.closest('form').submit();">
                {{ __('Logout') }}
            </x-jet-dropdown-link>
        </form>
    </body>
</html>
```

@canで特定の権限の場合のみ項目を表示するよう制御できる。

管理ユーザーでログインしたとき

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/32e98c91-f5aa-4f57-0114-f5fa6bfbd497.png)|
|:-:|

アドミンユーザーでログインしたとき

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/81e46c26-5a3a-7363-c0a5-81f7f7ab1ce2.png)|
|:-:|

秘密見みせるよ、というリンクが表示された。
クリックすると、ちゃんと秘密が見れる。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137858/dc516b1f-75a7-38d4-2e98-b75d956fac59.png)|
|:-:|

このくらいやっておけば、大体対応できるんじゃないだろうか。
基本的なことしかしてないので、みんなそれぞれ自由にアレンジして認証・認可ライフを楽しんでね。
いずれまた会おう。さらば！！

# APPENDIX ないしょのはまりポイント

いやー、今回もはまったはまった。簡単には動いてくれないよね。
今回のはまりポイントは、こちら！

## Routeが設定できないよ

Ver8からRouteの設定方法が変わった。のはググってすぐわかったが、じゃあどうしたらいい？というのが結構わからなかった。
こんなポイントに注意だったよ。
・使うControllerはuseで定義する
今まではControllerを設定しなくてよかったが、Ver8からはuseの設定が必要になった。
・そのためControllerのパスは書かなくてよくなった
今までは/app/Http/Controllersの下のパスも書かなきゃならなかったが、useで指定しているのでパスは書かず、Controller名だけ書けばよくなった。
・[]はresourceで設定するときは書かなくていい
パラメータでfunction名を書くときは[]でくくらなきゃダメだけど、resource指定の時のようにfunction名を書かない場合は[]をつけるとエラーになる。いいじゃん、つけても、って思うけど、エラーだ。

## url直接指定できなかったよ

外のurlへは直接リンク指定で来たんだけど、自分のプロジェクト内でhtmlファイルを作ってリンク先にジャンプしようと思ったら、上手く出来なかった。
多分やり方はあると思うが、上手く出来なかったので結局普通にControllerで設定することにした。がっでむ。