【laravel5.7】 MultiAuthにEmailVerificationを対応させる

MultiAuthにEmailVerificationを対応させる実装メモ。
[【laravel5.7】 MultiAuthログイン](https://qiita.com/kana-t/items/196fa542746db3a6a565)の続きとして書きます。

>MultiAuthなしの実装メモは別記事で書いているので、参考になれば、、、
>[【laravel5.7】 Email Verificationの使い方](https://qiita.com/kana-t/items/3777ee08d44203f13394)


##やりたいこと
MultiAuth（Admin/User）のUserのみにEmail Verificationを対応させたい。

##環境
MacOS 10.14.3
VisualStudio
laravel 5.7

##Email Verificationを使えるようにする
###MustVerifyEmailの追加

```diff:app/User.php
<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

+class User extends Authenticatable implements MustVerifyEmail
{

```

###ルーティング設定
```diff:routes/web.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

use Illuminate\Http\Request;

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');
// User
Route::group(['namespace' => 'User','prefix'=>'user'],function(){
+    Route::get('/',function(){
+        return redirect()->to('user/home');
+    })->name('user');
    // home
    Route::get('home','HomeController@index')->name('user.home');

    // login lgoout
    Route::get('login','Auth\LoginController@showLoginForm')->name('user.login');
    Route::post('login','Auth\LoginController@login')->name('user.login');
    Route::post('logout','Auth\LoginController@logout')->name('user.logout');

    // register
    Route::get('register','Auth\RegisterController@showRegisterForm')->name('user.register');
    Route::post('register','Auth\RegisterController@register')->name('user.register');

+    // emailverify
+    Route::middleware('throttle:6,1')->get('email/resend','Auth\VerificationController@resend')->name('user.verification.resend');
+    Route::middleware('throttle:6,1')->get('email/verify','Auth\VerificationController@show')->name('user.verification.notice');
+    Route::middleware('signed')->get('email/verify/{id}','Auth\VerificationController@verify')->name('user.verification.verify');
});

// Admin
以下省略
```
単体ログインのEmailVerificationの`Auth:routes(['verify'=>true]);`を
MultiAuthでは上の（`//emailverify以下`の部分）のように一つずつ書いていきます。

###VerificationController.php
```diff:app/Http/Controllers/User/Auth/VerificationController.php
<?php

+namespace App\Http\Controllers\User\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\VerifiesEmails;
+use Illuminate\Http\Request;


class VerificationController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Email Verification Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling email verification for any
    | user that recently registered with the application. Emails may also
    | be re-sent if the user didn't receive the original email message.
    |
    */

    use VerifiesEmails;

    /**
     * Where to redirect users after verification.
     *
     * @var string
     */
+    protected $redirectTo = '/user/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
+        $this->middleware('auth:user');
        $this->middleware('signed')->only('verify');
        $this->middleware('throttle:6,1')->only('verify', 'resend');
    }

+    public function show(Request $request)
+    {
+        return $request->user()->hasVerifiedEmail()
+                        ? redirect($this->redirectPath())
+                        : view('user.auth.verify');
+    }
}

```

###HomeController.php
```diff:app/Http/Controllers/User/HomeController.php
    public function __construct()
    {
        $this->middleware('auth:user');
+       $this->middleware('allVerified');
    }
```

###Middleware
AllEnsureEmailIsVerified.phpを作成します

```terminal:terminal
php artisan make:middleware AllEnsureEmailIsVerified
```

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

namespace App\Http\Middleware;

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

class AllEnsureEmailIsVerified
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $guards = array_keys(config('auth.guards'));

        foreach($guards as $guard) {
            if($guard == 'user') {
                if (Auth::guard($guard)->check()) {
                    if (! Auth::guard($guard)->user() ||
                        (Auth::guard($guard)->user() instanceof MustVerifyEmail &&
                        ! Auth::guard($guard)->user()->hasVerifiedEmail())) {
                        // dd('ddd');
                        return $request->expectsJson()
                                ? abort(403, 'Your email address is not verified.')
                                : Redirect::route('user.verification.notice');
                    }  
                }
            }
        }
        return $next($request);
    }
}

```

###Kernel.php
```diff:app/Http/Kernel.php
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
+       'allVerified' => \App\Http\Middleware\AllEnsureEmailIsVerified::class,
    ];
```

###verify.blade.php
```diff:views/user/auth/verify.blade.php
+@extends('layouts.user.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('メールアドレスの認証') }}</div>

                <div class="card-body">
                    @if (session('resent'))
                        <div class="alert alert-success" role="alert">
                            {{ __('認証メールを再送信しました。') }}
                        </div>
                    @endif

                    {{ __('メールアドレスの認証をしてください。') }}
+                    {{ __('もしメールを受け取ってないなら、<a href="{{ route('user.verification.resend') }}">ここをクリックしてください</a>。') }}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

```

##メール内容をカスタマイズ
###Notificationsの作成

```terminal:terminal
php artisan make:notification UserVerifyEmailNotification
```

```php:app/Notifications/UserVerifyEmailNotification.php
<?php

namespace App\Notifications;

use Illuminate\Auth\Notifications\VerifyEmail as VerifyEmailNotification;
use Illuminate\Notifications\Messages\MailMessage;

use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Lang;

class UserVerifyEmailNotification extends VerifyEmailNotification
{
    public function toMail($notifiable)
    {
        if (static::$toMailCallback) {
            return call_user_func(static::$toMailCallback, $notifiable);
        }

        return (new MailMessage)
            ->subject(Lang::getFromJson('USERメール確認'))
            ->line(Lang::getFromJson('クリックして認証してください.'))
            ->action(
                Lang::getFromJson('メール認証'),
                $this->verificationUrl($notifiable)
            )
            ->line(Lang::getFromJson('もしこのメールに覚えが無い場合は破棄してください。'));
    }


    protected function verificationUrl($notifiable)
    {
        return URL::temporarySignedRoute(
            'user.verification.verify', Carbon::now()->addMinutes(60), ['id' => $notifiable->getKey()]
        );
    }
}

```

###User.php
UserVerifyEmailNotificationがメールで送信されるようにModelに設定します。

```diff:app/User.php
<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

+use App\Notifications\UserVerifyEmailNotification;

class User extends Authenticatable implements MustVerifyEmail
{
    use Notifiable;

    /**
     * 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',
    ];

+    public function sendEmailVerificationNotification()
+    {
+        $this->notify(new UserVerifyEmailNotification);
+    }
}

```

##動作確認
User
http://127.0.0.1:8000/user/register から新しくアカウントを登録します。

web
<img width="645" alt="スクリーンショット 2019-03-01 11.27.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/252667/64f75f56-483c-1b4e-89e0-be1b8e1074f2.png">


登録したメールアドレス
<img width="697" alt="スクリーンショット 2019-02-28 11.48.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/252667/04da949e-b84c-6e1c-c610-364eaa35409b.png">


メール認証ボタンクリックすると、
<img width="1056" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3235323636372f37653335643530392d353037382d663966382d653163332d6335396463303931653936382e706e67.png" src="https://qiita-image-store.s3.amazonaws.com/0/252667/0e8b1539-647e-aa37-3e84-cf6c83a70532.png">


こうなれば成功です。
まだ英語の部分があるので、その辺の設定は後々設定しようと思います。

Adminでも実装したい場合はこれと同様に編集すればOK。
とりあえず以上です。