Laravel8 マルチログイン時のメール認証

## 初めに

Laravel8でマルチログイン、メール認証それぞれの単体記事はたくさんあるのですが、マルチログインを実装した際のメール認証がなく私自身が少し詰まったので、まとめようと思います。初めての記事投稿ですので、お手柔らかに🙇‍♂️

## 環境

```php
PHP: 8.0.9
Laravel: 8.52.0
laravel/breeze: 1.4
```

## 準備

マルチログインの実装に関する記事はたくさんあるので、マルチログイン実装部分は割愛させていただきます。
ModelはUserとAdminを作成して、prefixとasをつけています。

```php:RouteServiceProvider.php
    public function boot()
    {
        $this->configureRateLimiting();

        $this->routes(function () {
            Route::prefix('api')
                ->middleware('api')
                ->namespace($this->namespace)
                ->group(base_path('routes/api.php'));

            Route::prefix('admin')
                ->as('admin.')
                ->middleware('web')
                ->namespace($this->namespace)
                ->group(base_path('routes/admin.php'));
            
            Route::prefix('/')
                ->as('user.')
                ->middleware('web')
                ->namespace($this->namespace)
                ->group(base_path('routes/web.php'));
        });
    }
```

- [Breeze](https://readouble.com/laravel/8.x/ja/starter-kits.html#laravel-breeze)

- [Guard](https://readouble.com/laravel/8.x/ja/authentication.html)

- mailの確認は、[mailtrap](https://mailtrap.io)を使用しています。


## 実装

まずは、ドキュメントを確認します。
Laravel8では簡単に[メール認証を組み込む方法](https://readouble.com/laravel/8.x/ja/verification.html)を用意してくれています。

ドキュメント通りに実装し、いざ新規登録をしてみると、、、

![スクリーンショット 2022-01-16 17.16.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666908/7a725c5d-4b4e-980b-aa27-ad2bf184fae7.png)

先ほど、RouteServiceProvider.phpでasを指定したため、web.php, auth.phpのルートは先頭に(user.)がつきます。
そのため、verification.verifyが定義されてないよと怒られてしまいます。

また、以下の部分でパスを指定しています。

```php:vendor/laravel/framework/src/Illuminate/Auth/Notifications/VerifyEmail.php
 protected function verificationUrl($notifiable)
    {
        if (static::$createUrlCallback) {
            return call_user_func(static::$createUrlCallback, $notifiable);
        }
        return URL::temporarySignedRoute(
            'verification.verify', ←ここ

            Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
            [
                'id' => $notifiable->getKey(),

                'hash' => sha1($notifiable->getEmailForVerification()),
            ]
        );
    }
```

vendor配下のコードを直接変更するのはよろしくないので、このクラスをオーバーライドしていきます。

メール通知なのでNotification配下に作成します。vendor/laravel/framework/src/Illuminate/Auth/Notifications/VerifyEmail.phpの中身を作成したVerifyEmailにまるっとコピーしてきて、パスを指定している部分を変更します。

```php
 php artisan make:notification User/VerifyEmail
```

```php:app/Notifications/User/VerifyEmail.php
<?php

namespace App\Notifications\User;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\URL;

class VerifyEmail extends Notification
{
    use Queueable;

        /**
     * The callback that should be used to create the verify email URL.
     *
     * @var \Closure|null
     */
    public static $createUrlCallback;

    /**
     * The callback that should be used to build the mail message.
     *
     * @var \Closure|null
     */
    public static $toMailCallback;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Build the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        $verificationUrl = $this->verificationUrl($notifiable);

        if (static::$toMailCallback) {
            return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
        }

        return $this->buildMailMessage($verificationUrl);
    }

        /**
     * Get the verify email notification mail message for the given URL.
     *
     * @param  string  $url
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    protected function buildMailMessage($url)
    {
        return (new MailMessage)
            ->subject(Lang::get('Verify Email Address'))
            ->line(Lang::get('Please click the button below to verify your email address.'))
            ->action(Lang::get('Verify Email Address'), $url)
            ->line(Lang::get('If you did not create an account, no further action is required.'));
    }

        /**
     * Get the verification URL for the given notifiable.
     *
     * @param  mixed  $notifiable
     * @return string
     */
    protected function verificationUrl($notifiable)
    {
        if (static::$createUrlCallback) {
            return call_user_func(static::$createUrlCallback, $notifiable);
        }

        return URL::temporarySignedRoute(
            'user.verification.verify',// ←ここ
            Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
            [
                'id' => $notifiable->getKey(),
                'hash' => sha1($notifiable->getEmailForVerification()),
            ]
        );
    }

        /**
     * Set a callback that should be used when creating the email verification URL.
     *
     * @param  \Closure  $callback
     * @return void
     */
    public static function createUrlUsing($callback)
    {
        static::$createUrlCallback = $callback;
    }

    /**
     * Set a callback that should be used when building the notification mail message.
     *
     * @param  \Closure  $callback
     * @return void
     */
    public static function toMailUsing($callback)
    {
        static::$toMailCallback = $callback;
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}
```

そして以下を追加。これは、MustVerifyEmail内のメソッドで新規登録時に送信されるメールの処理です。

```php:Models/User.php
public function sendEmailVerificationNotification(){
   $this->notify(new VerifyEmail());
}
```

これでいける！！！
と思いきや、
![スクリーンショット 2022-01-16 18.27.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666908/7199bd67-1d9a-1f44-3e4e-a880c075963f.png)

これは、middlewareのEnsureEmailIsVerifiedで先ほどと同じようにバスを指定している部分でエラーが発生しています。
web.phpなどで指定しているverifiedですね。

```php:app\Http\Kernel.php
...省略
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    ];
```

```php:web.php
Route::get('/dashboard', function () {
    return view('user.dashboard');
})->middleware(['auth:users', 'verified'])->name('dashboard');
```


app\Http\Middleware\EnsureEmailIsVerified.phpを作成し、\Illuminate\Auth\Middleware\EnsureEmailIsVerifiedをまるっとコピーします。namespaceは変更する必要があります。get_classでオブジェクトのクラス名を取得して条件分岐。

```php:CustomEnsureEmailIsVerified.php
<?php

namespace App\Http\Middleware; ←ここ

use Closure;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\URL;

class CustomEnsureEmailIsVerified
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $redirectToRoute
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse|null
     */
    public function handle($request, Closure $next, $redirectToRoute = null)
    {
        if (! $request->user() ||
            ($request->user() instanceof MustVerifyEmail &&
            ! $request->user()->hasVerifiedEmail())) {

                
                if(get_class($request->user()) === 'App\Models\User') { ←ここ
                    $path = 'user.';
                } else {
                    $path = 'admin.';
                }
            return $request->expectsJson()
                    ? abort(403, 'Your email address is not verified.')
                    : Redirect::guest(URL::route($redirectToRoute ?: $path. 'verification.notice'));
        }

        return $next($request);
    }
}
```

あとは送られてきたメールからアクセスし、ログインできれば完了です！！
もう一つのモデルも同じように作成すればOKです。

![スクリーンショット 2022-01-18 6.18.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666908/fd794026-57d4-997d-e29f-30496de017fb.png)

## 参考
https://readouble.com/laravel/8.x/ja/verification.html

もし間違っている点や、もっと良い書き方などあればぜひ教えてください🙇‍♂️