【Laravel】スターターキットを使用しないマルチログイン認証の実装方法 

## この記事に書いてあること
マルチログイン認証の実装方法

## 利用バージョン
Laravel8

## モデル＆マイグレーションをつくる
管理者用のモデルとマイグレーションをつくる
※一般ユーザー用のモデルとマイグレーションはデフォルトで入っているものを利用

以下のコマンドを実行

```
php artisan make:model Admin -m
```

### モデルの設定
`app/Models/User.php`の中身をまるっと`Admin.php`にコピペ
クラス名の変更を必ずする

```php:app/Models/Admin.php
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

//クラス名変える
class Admin extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
    //省略
}
```

### マイグレーションをつくる
モデルと同じように、`database/migrations/create_users_table.php`の中身をまるっと、create_admins_table.phpへコピペ
テーブル名の変更を必ずする

```php:database/migrations/create_admins_table.php
<?php

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

class CreateAdminsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        //テーブル名変更
        Schema::create('admins', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        //テーブル名変更
        Schema::dropIfExists('admins');
    }
}
```

## テストデータをつくる
管理者と一般ユーザーのテストデータを作ります

以下コマンドでシーダーファイルを作成
```
php artisan make:seed MultiAuthTableSeeder
```

Seederファイル（テストデータの設定ファイル）が作成されるので中身を以下のように変更

```php:database/seeders/MultiAuthTableSeeder.php
<?php

namespace Database\Seeders;

use App\Models\Admin;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Database\Seeder;

class MultiAuthTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $init_users = [
            [
                'name' => '一般ユーザー',
                'email' => 'user@test.com',
                'password' => 'secret',
            ],
        ];

        foreach($init_users as $init_user) {

            $user = new User();
            $user->name = $init_user['name'];
            $user->email = $init_user['email'];
            $user->password = Hash::make($init_user['password']);
            $user->save();

        }

        $init_admins = [
            [
                'name' => '管理者',
                'email' => 'admin@test.com',
                'password' => 'secret',
            ],
        ];

        foreach($init_admins as $init_admin) {

            $admin = new Admin();
            $admin->name = $init_admin['name'];
            $admin->email = $init_admin['email'];
            $admin->password = Hash::make($init_admin['password']);
            $admin->save();

        }
    }
}
```

SeederファイルをDatabaseSeederに登録

```php:database/seeders/DatabaseSeeder.php
public function run()
{
    $this->call(MultiAuthTableSeeder::class);
}
```

以下のコマンドでテーブルとテストデータをデータベースに作成

```
php artisan migrate:fresh --seed
```

## 各モデルでログインできるように設定する
複数のログイン機能をつくるには、ガード・プロバイダ・モデルがそれぞれ必要になります。
ここら辺の説明は[こちら](https://zenn.dev/ad5/articles/48671b32c89897)の記事を参照ください。

モデルはさっき作ったので、ガードとプロバイダを追加していきます。
これらは`config/auth.php`で管理されているので、そちらを編集していく。

```php:config/auth.php
<?php

return [
    //省略
    
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        //追加
        'users' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        //追加
        'admins' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],
    ],
    
    //省略
    
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        //追加
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],
    ],
    
    //省略
];
```

## コントローラーをつくる

以下のコマンドを実行

```
artisan make:controller MultiAuthController
```

作成されたコントローラを以下のように変更

メソッドの役割
- showLoginForm()：ブラウザでメールアドレスやパスワードを入力するページ
- showUserDashBoard()：一般ユーザーログイン後のダッシュボード
- showAdminDashBoard()：管理者ログイン後のダッシュボード
- login()：ログイン画面からのフォーム入力情報送信先
- logout()：ダッシュボードページからのログアウト情報送信先

LoginとLogoutメソッドの定義方法はドキュメントにそれぞれ記載があるので、それらをまるっとコピペしてます。
[Loginメソッド](https://readouble.com/laravel/8.x/ja/authentication.html#authenticating-users)
[Logoutメソッド](https://readouble.com/laravel/8.x/ja/authentication.html#logging-out)

```php:app/HTTP/Controllers/MultiAuthController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class MultiAuthController extends Controller
{
    public function showLoginForm() {
        return view('multi_auth.login');
    }

    public function showUserDashBoard() {
        return view('multi_auth.userDashBoard');
    }

    public function showAdminDashBoard() {
        return view('multi_auth.adminDashBoard');
    }

    /**
     * 認証の試行を処理
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function login(Request $request)
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);
        $credentials = $request->only(['email', 'password']);
        $guard = $request->guard;

        if (Auth::guard($guard)->attempt($credentials)) {
            $request->session()->regenerate();

            return redirect($guard.'/dashboard');
        }

        return back()->withErrors([
            'email' => 'The provided credentials do not match our records.',
        ]);
    }

    /**
     * ユーザーをアプリケーションからログアウトさせる
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
    */
    public function logout(Request $request)
    {
        Auth::logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        return redirect('multi_login');
    }
}
```

## ビューをつくる
### showLoginForm()の中で指定したビュー

```php:resources/views/multi_auth/login.blade.php
<html>
<head>
    <link href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
    <form method="POST" action="multi_login">
        @csrf
        <div class="p-3">
            @foreach ($errors->all() as $error)
                <li class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-3">{{$error}}</li>
            @endforeach

            <label class="block">メールアドレス</label>
            <input class="border rounded mb-3 px-2 py-1" type="text" name="email">
            <label class="block">パスワード</label>
            <input class="border rounded mb-3 px-2 py-1" type="password" name="password">
            <label class="block">ユーザータイプ</label>
            <select name="guard" class="border rounded px-2 py-1 mb-5">
                <option value="">▼選択してください</option>
                <option value="users">一般ユーザー</option>
                <option value="admins">管理者</option>
            </select>
            <br>
            <button class="bg-blue-500 text-white rounded px-3 py-2" type="submit">ログイン</button>
        </div>
    </form>
</body>
</html>
```

### showUserDashBoard()の中で指定したビュー

```php:resources/views/multi_auth/userDashBoard.blade.php
<html>
<head>
    <link href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
    一般ユーザーのダッシュボード
    <form method="POST" action="/multi_login/logout">
        @csrf
        <div class="p-3">
            <button class="bg-blue-500 text-white rounded px-3 py-2" type="submit">ログアウト</button>
        </div>
    </form>
</body>
</html>
```

### showAdminDashBoard()の中で指定したビュー

```php:resources/views/multi_auth/adminDashBoard.blade.php
<html>
<head>
    <link href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
    管理者のダッシュボード
    <form method="POST" action="/multi_login/logout">
        @csrf
        <div class="p-3">
            <button class="bg-blue-500 text-white rounded px-3 py-2" type="submit">ログアウト</button>
        </div>
    </form>
</body>
</html>
```

## ルートをつくる

ミドルウェアのauthは:に続けてコンフィグで設定したユーザー名を記載
自動的にそのユーザーのログインを判別してくれる

```php:routes/web.php
<?php

use Illuminate\Support\Facades\Route;

// マルチ認証
// ログイン
Route::get('multi_login', [\App\Http\Controllers\MultiAuthController::class, 'showLoginForm']);
Route::post('multi_login', [\App\Http\Controllers\MultiAuthController::class, 'login']);

// ログアウト
Route::post('multi_login/logout', [\App\Http\Controllers\MultiAuthController::class, 'logout']);

// ログイン後のページ
Route::prefix('users')->middleware('auth:users')->group(function(){
    Route::get('dashboard', [\App\Http\Controllers\MultiAuthController::class, 'showUserDashBoard']);
});
Route::prefix('admins')->middleware('auth:admins')->group(function(){
    Route::get('dashboard', [\App\Http\Controllers\MultiAuthController::class, 'showAdminDashBoard']);
});
```

## ログインしていないときの強制リダイレクトをつくる
デフォルトだと、リダイレクト先が通常のログインページになっているので変更

```php:app/Http/Middleware/Authenticate.php
<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    protected function redirectTo($request)
    {
        if (! $request->expectsJson()) {
            return 'multi_login';//変更
        }
    }
}
```

おしまい。

## 所感
認証の仕組みを俯瞰して理解すると簡単・シンプルに実装できました。

## 参考記事
https://zenn.dev/ad5/articles/48671b32c89897