【Laravel】 Breezeの認証周りを完全に理解する
Webアプリケーションを作成するにあたって必須と言えるものがユーザ認証です。アカウントを作成し、ログインした状態でしかできない操作を提供します。まずは最小構成でユーザ認証を作ってみましょう。
目次
Laravel Breeze
Laravel Breezeは、Laravelの認証機能を最小限にシンプルに実装したものです。そのままで使用できますが、ここではどのような構成になっているか見て、自力で作れるように理解できると強みになります。
Breezeを導入すると、ルーティングなどの一部のファイルが上書きされます。そのため、新規のプロジェクトでインストールするべきです。
Breeze導入
Laravel Breezeは、composerで導入することができます。
composer require laravel/breeze --dev
導入したらbreeze:install
します。
php artisan breeze:install
最後にnpm install && npm run dev
してくれと表示されるので、実行して導入は完了です。
様々なファイルが作成されているので、動作を見ていきましょう。
アカウントの作成
ルートの定義を見る
/register
に移動すると、アカウント作成画面が表示されます。この画面は、app/routes/auth.php
にあるRoute::get('register'...
によってルートが定義されています。コントローラはRegisteredUserController
ですね。
Route::middleware('guest')->group(function () { Route::get('register', [RegisteredUserController::class, 'create']) ->name('register'); Route::post('register', [RegisteredUserController::class, 'store']); // ... });
Route::middleware('guest')->group(...)
内にあるので、guest
というミドルウェアが使用されているようです。ミドルウェアはapp/Http/Kernel.php
で名前を付けていました。そこを見ると\App\Http\Middleware\RedirectIfAuthenticated::class
が使用されていることが分かります。
protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, // ... ];
ミドルウェアの定義を見ると、ログイン済みユーザはhome画面に移動させられるという構造になっています。
public function handle(Request $request, Closure $next, ...$guards) { $guards = empty($guards) ? [null] : $guards; foreach ($guards as $guard) { if (Auth::guard($guard)->check()) { return redirect(RouteServiceProvider::HOME); } } return $next($request); }
guards
は、使用するguard
の種類です。例えばguest:admin
のように:
の後に指定します。何もなければ、app/config/auth.php
にあるデフォルトのguard
が使用されます。
ミドルウェアに関しては下の記事に載せています。
ビューの定義を見る
コードを追うと、ビューはapp/resources/views/auth/register.blade.php
にあることが分かります。このビューではコンポーネントが利用されています。
コンポーネントについては、ビューの記事に書いています。
随所にある__('Name')
は、多言語対応のためのものです。
あとは適当にフォームが並んでいるだけの普通のWebページです。
Register時の流れを見る
アカウント登録時は情報を送信するわけですが、<form method="POST" action="{{ route('register') }}"></form>
となっているので、routesを確認すると同コントローラのstore
に飛ぶことが分かります。
バリデーションをしたら、そのままUser::create
でモデルを作成して保存していることが分かります。create
できるのは、モデル内で$fillable
に含まれているからですね。
// ユーザをDBに登録 $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), ]);
登録されたユーザは、Users
テーブルに記録されています。
アカウントの登録は、User
モデルを作成後、Registerd
を使用してイベントを発火しています。EventServiceProvider
を見ると、このイベントでメアド認証用メールを送信しているように見えますが、実際には認証はありません。
event(new Registered($user));
class EventServiceProvider extends ServiceProvider { protected $listen = [ // event関数にRegisterdクラスが入ったらSendEmailVerificationNotificationに送られる Registered::class => [ SendEmailVerificationNotification::class, ], ]; // ... }
イベントに関して下の記事に載せています。
Breezeでは、メール認証はしない構成になっています。もし認証したい場合、app/Models/User.php
でMustVerifyEmail
をimplementする必要があります。
use Illuminate\Contracts\Auth\MustVerifyEmail; // implements MustVerifyEmail をつける class User extends Authenticatable implements MustVerifyEmail { // ... }
これで登録後にメールが送信されるようになります。
送信されたメールは、Laravel Sailで構築している場合、ローカルではhttp://localhost:8025/
にアクセスすると見れます。
後はログイン処理をして移動しています。
メール認証
上の作業でメール認証を行う場合、EventServiceProvider
を見るとメールが送信されていることが分かります。
メールに埋め込まれているアドレスとルーティングを見ると、http://.../verify-email/{id}/{hash}
のGETメソッドが対応しているようです。これはVerifyEmailController
の__invoke
に飛んでいるらしいので、内容を見てみましょう。
まずは、ルートを見るとmiddleware(['signed', 'throttle:6,1'])
とあります。このsigned
という名前のミドルウェアで、URLの{hash}
が合っているかどうかをチェックしています。これはもとからあるミドルウェアなので、実装せずともそのまま使えます。
このミドルウェアは、Kernel.php
を見ると'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
とあります。これが{hash}
のチェックをしている本体です。
ハッシュのチェックをクリアしてコントローラに入ると、最初はすでに認証済みかどうか見ているようです。認証済みなら無条件で飛びます。
if ($request->user()->hasVerifiedEmail()) { return redirect()->intended(RouteServiceProvider::HOME.'?verified=1'); }
redirect()->intended($path)
は、セッションの以前のURLに遷移するためのものです。
認証がまだなら、markEmailAsVerified
で認証済みマークを付けています。すでにハッシュのチェックは済んでいるので、users.email_verifies_at
に日付を入れているだけのようです。
認証済みマークを付けたらevent(new Verified($request->user()));
でイベントを投げていますが、対応するリスナーがないため、何も起こりません。何かしたい場合はリスナーを紐付けましょう。
hasVerifiedEmail
やmarkEmailAsVerified
は、app/Models/User.php
から親クラスを追っていくと実装されていることが分かります。
追っていった先のvendor\laravel\framework\src\Illuminate\Foundation\Auth\User.php
を見ると、細かい使い方はともかくとして、どのような機能があるかは何となく分かると思います。
処理が済んだらメインの画面に移ります。
ログイン
URLとルートを照らし合わせると、
Route::middleware('guest')->group(function () { //... Route::get('login', [AuthenticatedSessionController::class, 'create']) ->name('login'); //... });
が該当しますね。
ここからF12でたどると、vendor
内のファイルが開かれる場合があります。しかし、実際にはapp
内のほうが使用されます。
ログインページのcreate
メソッドを見るとただページを出しているだけです。
ログイン画面のビューのコードを見ると、name
がlogin
のページにPOSTしていることがわかるので、AuthenticatedSessionController@store
を呼んでいることが分かります。
public function store(LoginRequest $request) { $request->authenticate(); $request->session()->regenerate(); return redirect()->intended(RouteServiceProvider::HOME); }
みたまま、$request->authenticate();
がメインのログイン処理ですね。追っていくとAuth::attempt
で認証しています。ここで、
throw ValidationException::withMessages([ 'email' => trans('auth.failed'), ]);
で例外を排出していますが、実際にはエラー表示ではなくログイン画面に戻される挙動です。Laravelに組み込まれているValidationException
は例外を扱うものですが、排出した際のページ遷移の挙動を設定できるものになっています。例えば、
throw ValidationException::withMessages([ 'email' => trans('auth.failed'), ])->redirectTo('/');
のようにredirectTo
を設定すると、バリデーション失敗時にhttp://.../
に移動します。
これらの処理と同時に、ログイン失敗が多いとスロットリングするためのカウントとチェックも行われていますね。
ユーザ名とパスワードのチェックをするバリデーションを通過したらセッションを作成して終わりです。
ログアウト
ルートの定義は次のものであることが分かります。
Route::middleware('auth')->group(function () { // ... Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) ->name('logout'); });
ログインと同じコントローラのdestory
を指定しています。Route::middleware('auth')
とあり、Kernel.php
を見ると'auth' => \App\Http\Middleware\Authenticate::class,
のミドルウェアが使用されています。認証されていなければログインページに飛ばすだけのミドルウェアです。
後はログアウト処理です。
Auth::guard('web')->logout(); $request->session()->invalidate();
では、そのままログアウトしています。Auth::guard('web')->logout();
がログアウトのメインですが、セッションを破棄することでもログインは不可能になります。Auth::guard('web')->logout();
の方では、いわゆるremember me
の処理やLogoutをイベントで投げるなどをしているようです。
パスワードを忘れた
リセット用メールの送信
ルートは
Route::middleware('guest')->group(function () { // ... Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) ->name('password.request'); Route::post('forgot-password', [PasswordResetLinkController::class, 'store']) ->name('password.email'); // ... });
ですね。最初にアクセスするのは、login.blade.php
を見るとfotgot-password
のgetメソッドであることが分かります。
パスワードを忘れた画面でEmailを送信すると、NewPasswordController@store
が呼ばれます。適当に入力をバリデートしたら、メインのリセット用メールの送信処理です。
$status = Password::sendResetLink( $request->only('email') );
Password::sendResetLink
はLaravel組み込みの機能です。パスワードリセット用トークンを作成し、メールを送信しています。あとは送信に成功したかを見て終わりです。
リセットのページ
ルートは
Route::middleware('guest')->group(function () { // ... Route::get('reset-password/{token}', [NewPasswordController::class, 'create']) ->name('password.reset'); Route::post('reset-password', [NewPasswordController::class, 'store']) ->name('password.update'); // ... });
ページの表示時は、特にトークンのチェックは行われていません。/reset-password/aabbcchoge
のようにどんなトークンを入れてもページは表示できます。ただ、フォームに入力して送信しても失敗するようにできています。
フォームは表示されているだけです。
値が送信されると、パスワード形式が正しいかなどの適当にバリデーションが行われた後、リセット処理が行われます。
$status = Password::reset( // リクエストの内、一部のパラメータだけ取り出し $request->only('email', 'password', 'password_confirmation', 'token'), // メインのリセット処理 function ($user) use ($request) { $user->forceFill([ 'password' => Hash::make($request->password), 'remember_token' => Str::random(60), ])->save(); event(new PasswordReset($user)); } );
リセット処理Password::reset
が使用されています。これはLaravelの組み込み機能ですが、実際にパスワードのハッシュを生成するのはなんと自力で実装が必要なようです。remember_token
はログイン状態保持のためのものです。
まとめ
Laravel Breezeの認証機能を一通り理解すると、ログイン処理の理解が深まると思います。同時に、ここでは出てこなかったメソッドも、実装を追うことでどのような機能があるかを把握できたりします。
Breezeは最低限の認証関連の機能を実装してあるので、必ず理解して、グループ管理などの複雑な認証機能も作れるようになっていきましょう。
