【準備編】Laravel 8（+jetstream, fortify）でマルチログイン


# はじめに
Laravel8から標準のユーザー認証機能がlaravel/uiからLaravel Jetstream + Laravel Fortifyに変更されました。新しい認証機能もlaravel/uiと同様に導入自体は非常に簡単で、かつ多機能（プロフィール管理、チーム管理、二段階認証などなど）、必要に応じてconfigファイルで利用する機能を選択することが可能です。その一方で認証処理の実装が変わっていることもあり、拡張するには少し手間がかかります（ました）。[ドキュメント](https://readouble.com/laravel/8.x/ja/authentication.html)では下記のように言及されていますが、
>Laravel Jetstreamが用意する認証スカフォールドを使う必要はないことに留意してください。このスカフォールドを使用しない選択をした場合、Laravelの認証クラスを直接使用し、ユーザーの認証管理を行う必要があります。

せっかくならば新しい認証機能使いたいぞ！
ということで、本記事では一般ユーザーと管理者ユーザーでログイン画面、ログイン後の画面を分けるマルチログインをLaravel JetstreamとLaravel Fortifyを使って実装していきます。
なお、Laravel Fortifyの詳しい処理を深く追ったり説明したり、ということはこの記事ではしません。

# 動作環境
本記事では全てローカルで動作させます

- PHP 7.4.11
- MySQL 8.0.21
- Laravel Framework 8.10.0

# マルチログインの仕様
- 一般ユーザー（User）、管理者（Admin）でログインユーザーとページを分ける
- ルーティングは以下とする（本記事ではドメインはlocalhost:8000として扱います）
    - 一般ユーザーログインページ
        - localhost:8000/login
    - 一般ユーザートップページ
        - localhost:8000/dashboard
    - 管理者ログインページ
        - localhost:8000/admin/login
    - 管理者トップページ
        - localhost:8000/admin/dashboard
- 両ユーザー共に未ログインの時にはトップページを表示することはできない（ログインページにリダイレクトさせる）

## プロジェクトの準備
- プロジェクトの生成（今回はmulti-authというプロジェクトにしています）
    - Blade + livewireを使用するので`Which Jetstream stack do you prefer?`は0を選択
    - teamsは使用しないので`Will your application use teams?`はnoを選択

```
$ laravel new multi-auth --jet


    |     |         |
    |,---.|--- ,---.|--- ,---.,---.,---.,-.-.
    ||---'|    `---.|    |    |---',---|| | |
`---'`---'`---'`---'`---'`    `---'`---^` ' '


Which Jetstream stack do you prefer?
  [0] livewire
  [1] inertia
 > 0

 Will your application use teams? (yes/no) [no]:
 > no

Installing laravel/laravel (v8.1.0)
  - Installing laravel/laravel (v8.1.0): Downloading (100%)         
Created project in /{your-workspace-root}/multi-auth
~~省略~~
Application ready! Build something amazing.
```

- プロジェクトが生成されたらアセットのビルドを実行

```
$ cd multi-auth
$ npm install && npm run dev
```

- .envのDB_DATABASEと同じ名前のDBを、ローカルのMySQLで作成してmigrationを実行

```Env:.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=multi_auth　←　これ
DB_USERNAME=root
DB_PASSWORD=
```

```
$ php artisan migrate

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (42.86ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (19.50ms)
Migrating: 2014_10_12_200000_add_two_factor_columns_to_users_table
Migrated:  2014_10_12_200000_add_two_factor_columns_to_users_table (22.99ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (25.19ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (39.09ms)
Migrating: 2020_10_14_073012_create_sessions_table
Migrated:  2020_10_14_073012_create_sessions_table (52.85ms)
```

- Adminモデルの作成とmigrationを実行
    - create_users_table.phpからupメソッドの中身をコピペ

```
$ php artisan make:model Admin -m
Model created successfully.
Created Migration: 2020_10_14_075325_create_admins_table
```

```PHP:create_admins_table.php
~~省略~~
    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->foreignId('current_team_id')->nullable();
            $table->text('profile_photo_path')->nullable();
            $table->timestamps();
        });
    }
```

```
$ php artisan migrate
Migrating: 2020_10_14_075325_create_admins_table
Migrated:  2020_10_14_075325_create_admins_table (31.63ms)
```

- Adminモデルの内容を修正（User.phpからコピペ）

```PHP: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\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;

class Admin extends Authenticatable
{
    use HasApiTokens;
    use HasFactory;
    use HasProfilePhoto;
    use Notifiable;
    use TwoFactorAuthenticatable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_recovery_codes',
        'two_factor_secret',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = [
        'profile_photo_url',
    ];
}

```

- 一般ユーザーと管理者をシーダーで生成できるようにする

```
$ php artisan make:seeder UserSeeder
Seeder created successfully.
$ php artisan make:seeder AdminSeeder
Seeder created successfully.
```

```PHP:UserSeeder.php
<?php

namespace Database\Seeders;

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

class UserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        User::create([
            'name' => '一般ユーザー',
            'email' => 'user@user.user',
            'password' => Hash::make('password'),
        ]);
    }
}
```

```PHP:AdminSeeder.php
<?php

namespace Database\Seeders;

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

class AdminSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Admin::create([
            'name' => '管理者',
            'email' => 'admin@admin.admin',
            'password' => Hash::make('password')
        ]);
    }
}
```

```PHP:DatabaseSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(UserSeeder::class);
        $this->call(AdminSeeder::class);
    }
}
```

```
$ php artisan db:seed 
Seeding: Database\Seeders\UserSeeder
Seeded:  Database\Seeders\UserSeeder (250.64ms)
Seeding: Database\Seeders\AdminSeeder
Seeded:  Database\Seeders\AdminSeeder (106.95ms)
Database seeding completed successfully.
```

- ビルトインサーバーを起動してブラウザでlocalhost:8000を表示
    - localhost:8000/loginにて一般ユーザーでログインできるか確認

```
$ php artisan serve

Starting Laravel development server: http://127.0.0.1:8000
[Mon Oct 1 00:00:00 0000] PHP 7.4.11 Development Server (http://127.0.0.1:8000) started
```
![スクリーンショット 2020-10-19 14.54.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115590/c3f5390c-3a53-a145-5c2c-7e7aa117f814.png)

![スクリーンショット 2020-10-19 14.53.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115590/d04a1a43-627d-4895-9979-7a8fe8ea1dd0.png)

# 無事ログインできれば準備編は終了
[実装編](https://qiita.com/nasteng/items/dfa9f6d252d887095f79)に続きます