Laravelの認証(web画面)

# 目次
Laravelの記事一覧は下記
[PHPフレームワークLaravelの使い方](https://qiita.com/toontoon/items/c4d0371e504c37f6576e)

# Laravelバージョン
動作確認はLaravel Framework 7.19.1で行っています

# 前提条件
[eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)](https://qiita.com/toontoon/items/76fa7f8845b20776077c)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています

[LaravelでJavaScript、CSSを使う](https://qiita.com/toontoon/items/6d7c88bc96bfa8fcb53a)
本記事は上記が完了している前提で書かれています
`npm install`を上記で完了しています
`npm install`が完了している前提で書かれています

[Laravelでメール送信する](https://qiita.com/toontoon/items/83f5e73e1cce69e1936c)
上記記事でLaravelからメール送信できるようにしています
本記事はメール送信ができるようになっている前提で書かれています

[Laravelでデータベースを扱う準備をする](https://qiita.com/toontoon/items/740b9575d2a323e93c0d)
[Laravelでテーブル作成](https://qiita.com/toontoon/items/2948a722a0ae7da2fb33)
[Laravelで初期データ投入](https://qiita.com/toontoon/items/f081f7f9f1667233480a)
[Laravelでデータベースを操作する(Eloquent編)](https://qiita.com/toontoon/items/97a49d1ea94e30bb8b48)
本記事は上記ので作成したデータベースとレコードを使用します

# 認証に必要なソース生成
コマンドラインで 
`cd sample`
`composer require laravel/ui`
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します

または、composer.jsonを修正

```composer.json
‥‥
    "require": {
‥‥
        "laravel/tinker": "^2.0",
        "laravel/ui": "^2.1"
    },
‥‥
```
"laravel/ui": "^2.1"追加しました
コマンドラインで 
`cd sample`
`composer update`
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します

上記`composer require laravel/ui`か`composer update`のどちらかをしてください

コマンドラインで 
`php artisan ui vue --auth`
eclipseプロジェクトを右クリック→リフレッシュ
/sample/app/Http/Controllers/Auth、/sample/resources/views/authに多くのファイルが現れます
/sample/database/migrations/xxxx_xx_xx_xxxxxx_create_password_resets_table.phpも現れます
/sample/routes/web.phpに2行
`Auth::routes();`
`Route::get('/home', 'HomeController@index')->name('home');`
追記されています

# Model修正
(1) /sample/app/User.phpを/sample/app/Models/User.phpに移動します
/sample/app/Modelsフォルダは[Laravelでデータベースを操作する(Eloquent編)](https://qiita.com/toontoon/items/97a49d1ea94e30bb8b48)で作成しました

(2) /sample/app/Models/User.php修正
`namespace App;`を`namespace App\Models;`に修正

(3) /sample/config/auth.php修正

```auth.php
‥‥
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
‥‥
```
providers->users->modelをApp\Models\User::classに修正

(4) /sample/app/Http/Controllers/Auth/RegisterController.php修正
`use App\User;`を`use App\Models\User;`に修正

(5) /sample/database/factories/UserFactory.php修正
`use App\User;`を`use App\Models\User;`に修正

#JavaScript、CSSビルド実行
コマンドラインで
`cd sample`
`npm run dev`

# 動作確認その1
`http://localhost/laravelSample/`
画面右上にLOGIN、REGISTERのリンクが現れました
REGISTERをクリックし、Name、E-Mail Address、Password、Confirm Passwordを入力してRegisterボタンをクリック
MySQLでlaravel_sampleデータベースを確認してみましょう
`select * from users;`
レコードが入っています

右上のユーザー名プルダウンからLogoutをクリック
ログアウトできました

右上のユーザー名プルダウンからLOGINをクリック
先ほど登録したE-Mail Address、Passwordを入力し、Loginボタンをクリック
ログインできました

# 自動生成物の確認
先ほど実行した`php artisan ui vue --auth`で生成されたソースの中身を確認しましょう
(1) web.phpの確認
/sample/routes/web.phpに追記された`Auth::routes();`は
/sample/vendor/laravel/ui/src/AuthRouteMethods.phpのauth()メソッドの戻り値を実行します
AuthRouteMethods#authはAuthRouteMethods#resetPassword、AuthRouteMethods#confirmPassword、AuthRouteMethods#emailVerification
を状況にしたがって呼び出します
見ての通り、AuthRouteMethods.phpは認証に必要なルーティングが書かれているだけです
自動生成されたルーティングをまとめると下記になります

```
// ログイン
get('login', 'Auth\LoginController@showLoginForm')
post('login', 'Auth\LoginController@login');

// ログアウト
post('logout', 'Auth\LoginController@logout')

// ユーザー登録
get('register', 'Auth\RegisterController@showRegistrationForm')
post('register', 'Auth\RegisterController@register');

// パスワード再発行メール送信前の操作
get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')
post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')
// パスワード再発行メール送信後の操作
get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')
post('password/reset', 'Auth\ResetPasswordController@reset')

// 特定URLアクセス時のパスワード入力要求
get('password/confirm', 'Auth\ConfirmPasswordController@showConfirmForm')
post('password/confirm', 'Auth\ConfirmPasswordController@confirm');

// ユーザー登録後のメールアドレス確認
get('email/verify', 'Auth\VerificationController@show')
get('email/verify/{id}/{hash}', 'Auth\VerificationController@verify')
post('email/resend', 'Auth\VerificationController@resend')

get('/home', 'HomeController@index')
```

また、web.phpに定義しているルーティングに->middleware('auth');をつけると認証済みユーザーのみアクセスできるurlになります
例
`Route::get('sample/jscss', 'SampleController@jscss')->middleware('auth');`
これで`http://localhost/laravelSample/sample/jscss`にアクセスしようとするとログイン認証を要求されます

(2) Controllerクラスの確認
上記ルーティングに書かれているコントローラークラスの中を見ると実処理はトレイトに書かれていることがわかります
各コントローラーの実処理が書かれているトレイトは/sample/vendor/laravel/ui/auth-backendに入っています
対応表です

|機能|app/Http/Controllers/Auth配下のコントローラー|vendor/laravel/ui/auth-backend配下のトレイト|
|:-----------------------------------:|:---------------------------:|:--------------------------:|
|特定URLアクセス時のパスワード入力要求|ConfirmPasswordController.php|ConfirmsPasswords.php       |
|パスワード再発行メール送信前の操作   |ForgotPasswordController.php |SendsPasswordResetEmails.php|
|ログイン、ログアウト                 |LoginController.php          |AuthenticatesUsers.php      |
|ユーザー登録                         |RegisterController.php       |RegistersUsers.php          |
|パスワード再発行メール送信後の操作   |ResetPasswordController.php  |ResetsPasswords.php         |
|ユーザー登録後のメールアドレス確認   |VerificationController.php   |VerifiesEmails.php          |

トレイト内の処理は読めば分かるので、紹介はすこしだけにします

・RegistersUsers.phpのregister
トレイトvendor/laravel/ui/auth-backend/RegistersUsers.phpのregisterメソッドは、app/Http/Controllers/Auth/RegisterController.phpのregisterメソッドとなります。つまり、ユーザー登録メソッドです
`event(new Registered($user = $this->create($request->all())));`
の`$this->create($request->all())`部分でRegisterController.phpのcreateが呼ばれ、その中でUser::createが実行されます。User::createでusersテーブルにレコードがinsertされます
`$this->guard()`はAuth::guard()が実行され、SessionGuardが返ってきます。`$this->guard()->login($user);`でログインです

・guard
guardは認証まわりの処理が書かれたクラスです
`Auth::guard()`の戻り値は[vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php](https://laravel.com/api/7.x/Illuminate/Auth/SessionGuard.html)です
Auth::guard()は/sample/config/auth.phpの記述に対応したguardを返します。
auth.phpのdefaults->guardの値がwebなので、auth.phpのguards->web->driverの値を取り、それがsessionなので、SessionGuardを返します。SessionGuardの`$this->provider`には/sample/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.phpが入っています。これもauth.phpで設定しているためです。auth.phpのproviders->users->driverがeloquentだからです
EloquentUserProviderの$this->modelにはApp\Models\Userが入っています。これもauth.phpで設定しているためです。auth.phpのproviders->users->modelがApp\Models\User::classだからです

・event
RegistersUsersでevent()が実行されています。event関数は与えられた引数のクラス名をキーにして、app/Providers/EventServiceProvider.phpの$listenから値を取得し、その値のクラスのhandleメソッドを実行します

・ログインの時に使う認証項目
デフォルトではログインの時、E-Mail AddressとPasswordを入力することになっていますが、入力項目をE-Mail Addressでないものにすることができます。なぜE-Mail AddressになっているかというとLoginController.phpで使っているトレイトvendor/laravel/ui/auth-backend/AuthenticatesUsers.php内のusername()関数が'email'を返しているからです。LoginControllerでusernameメソッドを定義しusersテーブルの別のカラムを返すようにすれば、emailではない項目を認証に使用できるようになります

・ユーザー登録の時に使う項目
ユーザー登録時の入力項目はデフォルトではname、email、passwordですが、もっと項目を増やしたい場合、tableにカラム追加し、viewにも項目を増やし、RegisterControllerのvalidatorメソッドとcreateに増やした項目の処理を追記し、App\Models\Userの$fillableにも増やした項目を追加します

・ログイン認証回数制限
ログイン認証を何回試みることを許可するか。その回数失敗したら何分間ログインできなくするかを設定できます
LoginControllerにmaxAttemptsとdecayMinutesというプロパティを定義します
`// 最大ログイン認証回数`
`protected $maxAttempts = 5;`
`// $maxAttempts回ログイン失敗したら何分ログインできなくするか`
`protected $decayMinutes= 1;`
これらはThrottlesLoginsというトレイトで使われています。
(コントローラー)app/Http/Controllers/Auth/LoginController.php->(トレイト)vendor/laravel/ui/auth-backend/AuthenticatesUsers.php->(トレイト)vendor/laravel/ui/auth-backend/ThrottlesLogins.php
maxAttempts()関数とdecayMinutes()関数が定義されています。そこで使われます。プロパティを定義しなかった場合、デフォルトで5回、ロック1分になっていることもわかります
また、hasTooManyLoginAttemptsが認証失敗制限回数になったか判定するメソッド、sendLockoutResponseがロックがかかったことを知らせるレスポンスを返すメソッドです

(3) viewの確認
/sample/resources/views/auth配下にviewは格納されています
説明が必要な難しい内容にはなっていません
画面の見た目は必ず変えると思います
/sample/resources/views/auth配下のbladeファイルを好きなように変更してください

# Authファサード
Authファサードという認証ユーザーに関する便利メソッドを利用できます
`use Illuminate\Support\Facades\Auth;`
`// 現在認証されているユーザーの取得`
`$user = Auth::user();`
`if (Auth::check()) {`
`    // ユーザーはログインしている`
`}`
上記のように簡単に認証済みユーザーを取得できたり、ログイン済みかどうか判定できます
このAuthファサードですが、Authファサード実行時呼ばれるのは、vendor/laravel/framework/src/Illuminate/Auth/AuthManager.phpです。このAuthManagerにメソッドが定義されていればAuthManagerのメソッドが呼ばれ、AuthManagerに定義されていないメソッドであれば、guardのメソッドが呼ばれます
Auth::user()ならAuthManagerにuserというメソッドは無いので、SessionGuardのuserメソッドが呼ばれます。Auth::check()ならAuthManagerにcheckというメソッドは無いので、SessionGuardのcheckメソッドが呼ばれています(SessionGuardのcheckメソッドは、SessionGuardで使われているGuardHelpersトレイトで実装されてます)
Auth::guard()なら、AuthManagerに定義されているのでAuthManagerのguardメソッドが呼ばれます
[AuthManager](https://laravel.com/api/7.x/Illuminate/Auth/AuthManager.html)
[SessionGuard](https://laravel.com/api/7.x/Illuminate/Auth/SessionGuard.html)

# ユーザー登録時のメールアドレス確認
ユーザー登録時に入力されたメールアドレス宛に実際にメールを送信し、そのメールに記載されているリンクをクリックしてもらい、メールアドレス確認をするように改修します

SMTPサーバーの設定をしてない方は下記記事を参考に事前にしておいてください
[Laravelでメール送信する](https://qiita.com/toontoon/items/83f5e73e1cce69e1936c)

メール送信される流れは下記です
ユーザー登録時
vendor/laravel/ui/auth-backend/RegistersUsers.phpのregisterメソッド内で
`event(new Registered($user = $this->create($request->all())));`
が呼ばれ、app/Providers/EventServiceProvider.phpによって登録されている
SendEmailVerificationNotificationクラスのhandleメソッドが実行され
app/Models/User.phpのsendEmailVerificationNotificationメソッドが呼ばれます
これでメールが送信されます
User.phpのsendEmailVerificationNotificationメソッドで呼んでいる`$this->notify`はメール送信を実行するメソッドです。notifyメソッド定義場所は下記です
User.php->(トレイト)vendor/laravel/framework/src/Illuminate/Notifications/Notifiable.php->(トレイト)vendor/laravel/framework/src/Illuminate/Notifications/RoutesNotifications.php
`$this->notify`の中では
vendor/laravel/framework/src/Illuminate/Notifications/ChannelManager.phpのsendを実行してメール送信しています

ではユーザー登録時メール送信するために必要なものを作っていきます

(1) メール本文の作成
/sample/resources/views/sample/registerUser.blade.phpファイル作成

```registerUser.blade.php
<html>
    <body>
        <a href="{{$url}}">メールアドレスの確認</a>
    </body>
</html>
```

(2) 通知クラスの作成
/sample/app/Notificationsフォルダ作成
/sample/app/Notifications/RegisterUser.phpファイル作成

```RegisterUser.php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Auth\Notifications\VerifyEmail;

class RegisterUser extends VerifyEmail
{
    use Queueable;

    public function toMail($notifiable)
    {
        $verificationUrl = $this->verificationUrl($notifiable);

        return (new MailMessage)
                ->subject('ユーザー登録')
                ->view('sample.registerUser', ['url' => $verificationUrl]);

    }

}
```

(3) Modelの修正
/sample/app/Models/User.php修正

```User.php
‥‥
use App\Notifications\RegisterUser;
‥‥
class User extends Authenticatable implements MustVerifyEmail
‥‥
    public function sendEmailVerificationNotification()
    {
        $this->notify(new RegisterUser());
    }
‥‥
```
use文追記
implements MustVerifyEmail追記
sendPasswordResetNotificationメソッド追加
これでvendor/laravel/framework/src/Illuminate/Auth/Listeners/SendEmailVerificationNotification.phpのhandleメソッド内のif文がtrueになります

(4) ルーティング修正
/sample/routes/web.php修正

```
‥
/* Auth::routes(); */ 
Auth::routes(['verify' => true]);
‥
```
Auth::routes()に引数['verify' => true]を渡す
これによって、vendor/laravel/ui/src/AuthRouteMethods.phpのauthメソッド内if ($options['verify'] ?? false)がtrueになり、emailVerificationメソッド内のルーティングが有効になります

# 動作確認その2
`http://localhost/laravelSample/`
画面右上のREGISTERをクリックし、Name、E-Mail Address、Password、Confirm Passwordを入力してRegisterボタンをクリック
入力したE-Mail Address充てにメールが送信されました
そのメールにあるリンクをクリックします
MySQLでlaravel_sampleデータベースを確認してみましょう
`select * from users;`
email_verified_atカラムにクリックした時刻が入っています

# パスワードリセットの実装
ユーザーがパスワードを忘れた場合のパスワードリセットを実装します

パスワードリセット希望時にはメール送信が行われ、そのメールに記載されているリンクからパスワード再設定画面に遷移します

メール送信される流れは下記です
パスワードリセット画面でSend Password Reset Linkボタンをクリック時
vendor/laravel/ui/auth-backend/SendsPasswordResetEmails.phpのsendResetLinkEmailメソッド内の$this->broker()でvendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBroker.phpを取得し、PasswordBrokerのsendResetLinkを実行します
PasswordBrokerのsendResetLink内で
app/Models/User.phpのsendPasswordResetNotificationメソッドが呼ばれます
これでメールが送信されます

ではそれを実行するために必要なものを作っていきます

(1) password_resetsテーブルの作成
先ほど`php artisan ui vue --auth`をしたときに
/sample/database/migrations/xxxx_xx_xx_xxxxxx_create_password_resets_table.php
ができています
これをデータベースに流し込みます

コマンドラインで 
`cd sample`
`php artisan migrate`
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します

MySQLでlaravel_sampleデータベースを確認してみましょう
`desc password_resets;`
password_resetsテーブルができました

(2) メール本文の作成
/sample/resources/views/sample/resetPassword.blade.phpファイル作成

```resetPassword.blade.php
<html>
    <body>
        <a href="{{$url}}">パスワードリセット</a>
        <div>このリンクは{{$limit}}分後に期限切れになります</div>
    </body>
</html>
```

(2) 通知クラスの作成
/sample/app/Notifications/ResetPassword.phpファイル作成

```ResetPassword.php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class ResetPassword extends Notification
{
    use Queueable;

    public $token;

    public function __construct($token)
    {
        $this->token = $token;
    }

    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        $url = url(route('password.reset', [
            'token' => $this->token,
            'email' => $notifiable->getEmailForPasswordReset(),
        ], false));

        return (new MailMessage)
                ->subject('パスワードリセット')
                ->view('sample.resetPassword', ['url' => $url, 'limit' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]);

    }

    public function toArray($notifiable)
    {
        return [];
    }
}
```

(3) Modelの修正
/sample/app/Models/User.php修正

```User.php
‥‥
use App\Notifications\ResetPassword;
‥‥
    public function sendPasswordResetNotification($token)
    {
        $this->notify(new ResetPassword($token));
    }
‥‥
```
use文追記
sendPasswordResetNotificationメソッド追加

# 動作確認その3
`http://localhost/laravelSample/`
画面右上のLOGINをクリックし、Forgot Your Password?リンクをクリック
E-Mail Addressを入力し、Send Password Reset Linkボタンをクリック
入力したE-Mail Address充てにメールが送信されました
そのメールにあるリンクをクリックします
Passwordを再設定する画面に遷移します