こんにちは、24卒エンジニアの @guppe です!
私は今年の5月から初めてWebフレームワークを使った開発を始めました。Laravelというフレームワークを選び、日々勉強と実践を重ねています。
Laravelの学習の初期段階では、動画や記事を参考にしてフレームワーク全体の概要をつかむことを意識していました。その後、実際にサービスを作る開発に挑戦してきましたが、今回はその前段階として取り組んだ Laravel Breezeの読解 について紹介します。
BreezeはLaravelの公式スターターキットのひとつで、認証の基本的な仕組みを素早くセットアップできる便利なツールです。今回の記事では、このBreezeのコードを読み解きながら、 guestユーザー時の認証処理 に焦点を当てて解説していきます。
Breezeをインストールすると何が追加されるの?
Breezeをインストールすると、いくつかの重要なファイルがプロジェクトに追加されます。その中には、Laravelアプリケーションでよく使われる ルート定義ファイル も含まれています。
Breezeが生成するルート定義ファイルには2種類ありますが、今回はそのうち認証に関連するルートが記述されている auth.php
に注目します。このファイルを通じて、 guestユーザー時にどのような処理が行われるのか を読み解いていきましょう。
auth.phpの中身を読み解く
auth.php
の中身は以下のような構成になっています。
このファイルでは、middleware を使用して guest
と auth
のルートが分けられています。
- guest: 未認証のユーザーが利用するルート
- auth: 認証済みのユーザーが利用するルート
以下にguestルートの詳細を解説します。
<?php
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Auth\ConfirmablePasswordController;
use App\Http\Controllers\Auth\EmailVerificationNotificationController;
use App\Http\Controllers\Auth\EmailVerificationPromptController;
use App\Http\Controllers\Auth\NewPasswordController;
use App\Http\Controllers\Auth\PasswordController;
use App\Http\Controllers\Auth\PasswordResetLinkController;
use App\Http\Controllers\Auth\RegisteredUserController;
use App\Http\Controllers\Auth\VerifyEmailController;
use Illuminate\Support\Facades\Route;
Route::middleware('guest')->group(function () {
Route::get('register', [RegisteredUserController::class, 'create'])->name('register');
Route::post('register', [RegisteredUserController::class, 'store']);
Route::get('login', [AuthenticatedSessionController::class, 'create'])->name('login');
Route::post('login', [AuthenticatedSessionController::class, 'store']);
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])->name('password.request');
Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])->name('password.email');
Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])->name('password.reset');
Route::post('reset-password', [NewPasswordController::class, 'store'])->name('password.store');
});
Route::middleware('auth')->group(function () {
Route::get('verify-email', EmailVerificationPromptController::class)->name('verification.notice');
Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
->middleware(['signed', 'throttle:6,1'])
->name('verification.verify');
Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
->middleware('throttle:6,1')
->name('verification.send');
Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])->name('password.confirm');
Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
Route::put('password', [PasswordController::class, 'update'])->name('password.update');
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout');
});
guestルートの構成
auth.php
の guest セクションでは、以下のルートが定義されています。
-
register
-
GET /register
: ユーザー登録フォームを表示 -
POST /register
: 新しいユーザーを登録
-
-
login
-
GET /login
: ログインフォームを表示 -
POST /login
: 認証処理を実行
-
-
forgot-password
-
GET /forgot-password
: パスワードリセット用のフォームを表示 -
POST /forgot-password
: パスワードリセットリンクを送信
-
-
reset-password
-
GET /reset-password/{token}
: 新しいパスワードの入力フォームを表示 -
POST /reset-password
: パスワードをリセット
-
registerルートの処理を追う
register
ルートでは、次の2つの処理を提供しています。
-
GET /register
: ユーザー登録フォームを表示 -
POST /register
: 新しいユーザーを登録
それぞれの処理について、具体的に見ていきましょう。
ユーザー登録フォームの表示
GET /register
のリクエストでは、ユーザー登録フォームを表示します。この処理はとてもシンプルで、auth.register
ビューを返すだけです。以下が対応するコントローラーのコードです。
public function create(): View
{
return view('auth.register');
}
Laravel Breezeの登録フォームは、デフォルトで提供されるBladeテンプレートを使用しており、フロントエンド部分をカスタマイズすることで、見た目や入力項目を変更できます。このメソッドは基本的に ビューのリダイレクト に特化しているため、複雑な処理はありません。
新しいユーザーの登録
次に、POST /register
のリクエストでは、実際に新しいユーザーを登録する処理が行われます。この処理では以下の3つのデータをフォームから受け取り、登録処理を進めます。
- 名前 (name)
- メールアドレス (email)
- パスワード (password)
送信されたデータは、まず バリデーション を通過する必要があります。以下が対応するコントローラーのコードです。
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
event(new Registered($user));
Auth::login($user);
return redirect(route('dashboard', absolute: false));
}
処理の流れ
-
バリデーション
- フォームから送信されたデータが期待通りかチェックします。例えば、
email
はメールアドレス形式であること、password
は確認用パスワードと一致することなどを確認します
- フォームから送信されたデータが期待通りかチェックします。例えば、
-
ユーザーの作成
- バリデーションを通過したデータを基に、
User::create
を使って新しいユーザーを作成します。このとき、Hash::make
を使用してパスワードをハッシュ化し、データベースに安全に保存します
- バリデーションを通過したデータを基に、
-
イベントの発行
- ユーザーが登録されたことを通知するために、
Registered
イベントが発行されます。このイベントは、例えばメール送信処理や通知に活用できます
- ユーザーが登録されたことを通知するために、
-
ログイン処理
- 作成したユーザーをそのままログイン状態にします
-
リダイレクト
- 最後に、
dashboard
ページにリダイレクトします。このリダイレクト先は、アプリケーションに応じて変更できます
- 最後に、
以下のようにフォーマットを揃えて、login
ルートに関する解説をまとめました。
loginルートでの認証処理を読み解く
login
ルートでは、次の2つの処理が提供されています。
-
GET /login
: ログインフォームを表示 -
POST /login
: ログイン認証処理を実行
それぞれの処理について詳しく見ていきます。
ログインフォームの表示
GET /login
のリクエストでは、ログインフォームを表示します。この処理は非常にシンプルで、auth.login
ビューを返すだけです。以下が対応するコントローラーのコードです。
public function create(): View
{
return view('auth.login');
}
Breezeではデフォルトでログインフォームが提供されており、このフォームをカスタマイズすることで入力項目やデザインを変更できます。
ログイン認証処理
次に、POST /login
のリクエストでは、ログイン認証が実行されます。この処理では、リクエストデータをもとにユーザーの認証を行い、成功すればダッシュボードにリダイレクトします。
対応するコントローラーのコードは以下のとおりです。
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(route('dashboard', absolute: false));
}
処理の流れ
-
カスタムリクエストの使用
- デフォルトの
Request
クラスではなく、LoginRequest
というカスタムリクエストが使用されています。このリクエストには、認証に必要なバリデーションルールや認証処理のロジックが含まれています
- デフォルトの
-
認証処理 (
authenticate
)-
LoginRequest
のauthenticate
メソッドで、メールアドレスとパスワードの組み合わせが正しいかをチェックします。認証に失敗した場合は、エラーメッセージを返します
-
-
セッションの再生成
- 認証に成功すると、セッションを再生成してセッション固定攻撃(Session Fixation Attack)を防ぎます
-
リダイレクト
- 最後に、ユーザーがアクセスを試みたページ(またはデフォルトの
dashboard
)にリダイレクトします
- 最後に、ユーザーがアクセスを試みたページ(またはデフォルトの
LoginRequestについて
LoginRequest
は、LaravelのFormRequest
を拡張したクラスです。このクラスでは、ログインフォームのバリデーションルールや、認証処理のロジックをひとまとめにしています。
バリデーションルール
以下のルールで、ログインフォームから送信されたデータが正しい形式であることを検証します。
public function rules(): array
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
認証処理
authenticate
メソッドでは、次の処理が行われます。
-
レート制限の確認
- ログイン試行回数が一定数を超えている場合、
Lockout
イベントを発行し、エラーをスローします
- ログイン試行回数が一定数を超えている場合、
-
認証試行
-
Auth::attempt
を使用して、メールアドレスとパスワードの組み合わせをチェックします。失敗した場合、試行回数を増加させ、エラーメッセージを返します
-
-
試行回数のリセット
- 認証成功時には、試行回数をリセットします
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
レート制限の仕組み
ログイン試行回数を制限し、セキュリティを強化します。以下のように、IPアドレスやメールアドレスを基にスロットリングを実現しています。
public function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
}
forgot-passwordルートでのパスワードリセット処理を読み解く
forgot-password
ルートでは、次の2つの処理が提供されています。
-
GET /forgot-password
: パスワードリセットリンクのリクエストフォームを表示 -
POST /forgot-password
: パスワードリセットリンクのリクエストを処理
それぞれの処理について詳しく見ていきます。
パスワードリセットリンクリクエストフォームの表示
GET /forgot-password
のリクエストでは、パスワードリセットリンクリクエスト用のフォームを表示します。この処理は非常にシンプルで、auth.forgot-password
ビューを返すだけです。以下が対応するコントローラーのコードです。
public function create(): View
{
return view('auth.forgot-password');
}
このフォームでは、ユーザーが登録済みの メールアドレス を入力し、パスワードリセットリンクをリクエストすることができます。
パスワードリセットリンクリクエストの処理
次に、POST /forgot-password
のリクエストでは、パスワードリセットリンクの送信処理が実行されます。対応するコントローラーのコードは以下のとおりです。
public function store(Request $request): RedirectResponse
{
$request->validate([
'email' => ['required', 'email'],
]);
$status = Password::sendResetLink(
$request->only('email')
);
return $status == Password::RESET_LINK_SENT
? back()->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
処理の流れ
-
バリデーション
- 入力されたメールアドレスが必須で、かつ正しい形式であることをチェックします
-
パスワードリセットリンクの送信
-
Password::sendResetLink
メソッドを使用して、入力されたメールアドレス宛にリセットリンクを送信します。このメソッドは以下のような2つの状態を返します:-
RESET_LINK_SENT
: リセットリンクの送信に成功 -
RESET_LINK_FAILED
: リセットリンクの送信に失敗
-
-
-
レスポンスの生成
- リセットリンクの送信に成功した場合は、
status
メッセージをセッションに保存して元の画面にリダイレクトします - 失敗した場合は、入力されたメールアドレスを保持しつつ、エラーメッセージを表示します
- リセットリンクの送信に成功した場合は、
メールの送信について
Password::sendResetLink
を使って、入力されたメールアドレス宛にリセットリンクを送信します。この処理では、Laravelの通知(Notification
)システムを利用してメールの送信が行われます。通知内容はカスタマイズ可能です
reset-passwordルートでのパスワードリセット処理を読み解く
reset-password
ルートでは、次の2つの処理が提供されています。
-
GET /reset-password/{token}
: パスワードリセットフォームを表示 -
POST /reset-password
: パスワードリセット処理を実行
それぞれの処理について詳しく見ていきます。
パスワードリセットフォームの表示
GET /reset-password/{token}
のリクエストでは、パスワードリセット用のフォームを表示します。この処理では、auth.reset-password
ビューが返されます。
対応するコントローラーのコードは以下の通りです。
public function create(Request $request): View
{
return view('auth.reset-password', ['request' => $request]);
}
ここでは、トークンやリクエスト情報をビューに渡しています。auth.reset-password
ビューには、新しいパスワードを入力するためのフォームが含まれています。
パスワードリセット処理
次に、POST /reset-password
のリクエストでは、パスワードリセット処理が実行されます。この処理では、送信されたデータをもとにパスワードが更新されます。
対応するコントローラーのコードは以下の通りです。
public function store(Request $request): RedirectResponse
{
$request->validate([
'token' => ['required'],
'email' => ['required', 'email'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$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));
}
);
return $status == Password::PASSWORD_RESET
? redirect()->route('login')->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
処理の流れ
-
バリデーション
- トークン、メールアドレス、パスワードが正しい形式で送信されていることを確認します
-
パスワードリセット処理
-
Password::reset
メソッドを使用して、ユーザーのパスワードをリセットします。このメソッドには、コールバックとしてパスワード更新処理が渡されます - 更新されたパスワードは、
Hash::make
を使って安全にハッシュ化されます
-
-
イベントの発行
- パスワードがリセットされると、
PasswordReset
イベントが自動的に発行されます。このイベントを利用して、通知の送信やログ記録など、追加の処理を実装することができます
- パスワードがリセットされると、
-
レスポンスの生成
- パスワードリセットが成功した場合は、
login
ページにリダイレクトし、ステータスメッセージをセッションに保存します - リセットが失敗した場合は、入力したメールアドレスを保持しつつ、エラーメッセージを表示します
- パスワードリセットが成功した場合は、
コールバック内の処理
Password::reset
メソッドのコールバック部分では、次のような操作が行われます。
-
パスワードの更新
ユーザーモデルに新しいパスワードを設定し、保存します -
リメンバートークンの更新
ユーザーがログイン状態を保持するためのトークンを再生成します -
イベントの発行
PasswordReset
イベントを発行して、他の処理(通知やログ記録)をトリガーします。
以下がコールバック部分のコードです。
function ($user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
まとめ
この記事では、Laravel Breezeのコードを読み解きながら、guestユーザー時の認証処理 を詳しく解説しました。
Breezeのコードを初めて見たときは、とても複雑に感じていましたが、研修や案件での経験を重ねるうちに仕組みを落ち着いて理解できるようになり、以前よりもシンプルに見えるようになりました。
今回の読解を通じて、Laravelがどのように認証処理を設計しているのか、そしてBreezeがそれをどのように簡潔に実装しているのかを学ぶことができました。読者の皆さんにも、この記事がBreezeやLaravelの認証機能を学ぶ手助けになれば嬉しいです。