Laravel Breezeで読み解く、guestユーザーの認証処理

こんにちは、24卒エンジニアの @guppe です！  
私は今年の5月から初めてWebフレームワークを使った開発を始めました。Laravelというフレームワークを選び、日々勉強と実践を重ねています。

Laravelの学習の初期段階では、動画や記事を参考にしてフレームワーク全体の概要をつかむことを意識していました。その後、実際にサービスを作る開発に挑戦してきましたが、今回はその前段階として取り組んだ **Laravel Breezeの読解** について紹介します。

BreezeはLaravelの公式スターターキットのひとつで、認証の基本的な仕組みを素早くセットアップできる便利なツールです。今回の記事では、このBreezeのコードを読み解きながら、 **guestユーザー時の認証処理** に焦点を当てて解説していきます。

https://laravel.com/docs/11.x/starter-kits#laravel-breeze

## Breezeをインストールすると何が追加されるの？

Breezeをインストールすると、いくつかの重要なファイルがプロジェクトに追加されます。その中には、Laravelアプリケーションでよく使われる **ルート定義ファイル** も含まれています。

Breezeが生成するルート定義ファイルには2種類ありますが、今回はそのうち認証に関連するルートが記述されている `auth.php` に注目します。このファイルを通じて、 **guestユーザー時にどのような処理が行われるのか** を読み解いていきましょう。

## auth.phpの中身を読み解く

`auth.php` の中身は以下のような構成になっています。  
このファイルでは、**middleware** を使用して `guest` と `auth` のルートが分けられています。

- **guest**: 未認証のユーザーが利用するルート
- **auth**: 認証済みのユーザーが利用するルート

以下にguestルートの詳細を解説します。

```auth.php
<?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** セクションでは、以下のルートが定義されています。

1. **register**  
   - `GET /register`: ユーザー登録フォームを表示  
   - `POST /register`: 新しいユーザーを登録  
2. **login**  
   - `GET /login`: ログインフォームを表示  
   - `POST /login`: 認証処理を実行  
3. **forgot-password**  
   - `GET /forgot-password`: パスワードリセット用のフォームを表示  
   - `POST /forgot-password`: パスワードリセットリンクを送信  
4. **reset-password**  
   - `GET /reset-password/{token}`: 新しいパスワードの入力フォームを表示  
   - `POST /reset-password`: パスワードをリセット  

## registerルートの処理を追う

`register`ルートでは、次の2つの処理を提供しています。

- **`GET /register`**: ユーザー登録フォームを表示  
- **`POST /register`**: 新しいユーザーを登録  

それぞれの処理について、具体的に見ていきましょう。

### ユーザー登録フォームの表示

`GET /register` のリクエストでは、ユーザー登録フォームを表示します。この処理はとてもシンプルで、`auth.register` ビューを返すだけです。以下が対応するコントローラーのコードです。

```php
public function create(): View
{
    return view('auth.register');
}
```

Laravel Breezeの登録フォームは、デフォルトで提供されるBladeテンプレートを使用しており、フロントエンド部分をカスタマイズすることで、見た目や入力項目を変更できます。このメソッドは基本的に **ビューのリダイレクト** に特化しているため、複雑な処理はありません。

### 新しいユーザーの登録

次に、`POST /register` のリクエストでは、実際に新しいユーザーを登録する処理が行われます。この処理では以下の3つのデータをフォームから受け取り、登録処理を進めます。

1. **名前 (name)**  
2. **メールアドレス (email)**  
3. **パスワード (password)**  

送信されたデータは、まず **バリデーション** を通過する必要があります。以下が対応するコントローラーのコードです。

```php
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));
}
```

#### 処理の流れ

1. **バリデーション**  
   - フォームから送信されたデータが期待通りかチェックします。例えば、`email` はメールアドレス形式であること、`password` は確認用パスワードと一致することなどを確認します

2. **ユーザーの作成**  
   - バリデーションを通過したデータを基に、`User::create` を使って新しいユーザーを作成します。このとき、`Hash::make` を使用してパスワードをハッシュ化し、データベースに安全に保存します

3. **イベントの発行**  
   - ユーザーが登録されたことを通知するために、`Registered` イベントが発行されます。このイベントは、例えばメール送信処理や通知に活用できます

4. **ログイン処理**  
   - 作成したユーザーをそのままログイン状態にします

5. **リダイレクト**  
   - 最後に、`dashboard` ページにリダイレクトします。このリダイレクト先は、アプリケーションに応じて変更できます

以下のようにフォーマットを揃えて、`login`ルートに関する解説をまとめました。

## loginルートでの認証処理を読み解く

`login`ルートでは、次の2つの処理が提供されています。

- **`GET /login`**: ログインフォームを表示  
- **`POST /login`**: ログイン認証処理を実行  

それぞれの処理について詳しく見ていきます。

### ログインフォームの表示

`GET /login` のリクエストでは、ログインフォームを表示します。この処理は非常にシンプルで、`auth.login` ビューを返すだけです。以下が対応するコントローラーのコードです。

```php
public function create(): View
{
    return view('auth.login');
}
```

Breezeではデフォルトでログインフォームが提供されており、このフォームをカスタマイズすることで入力項目やデザインを変更できます。

### ログイン認証処理

次に、`POST /login` のリクエストでは、ログイン認証が実行されます。この処理では、リクエストデータをもとにユーザーの認証を行い、成功すればダッシュボードにリダイレクトします。

対応するコントローラーのコードは以下のとおりです。

```php
public function store(LoginRequest $request): RedirectResponse
{
    $request->authenticate();

    $request->session()->regenerate();

    return redirect()->intended(route('dashboard', absolute: false));
}
```

#### 処理の流れ

1. **カスタムリクエストの使用**  
   - デフォルトの`Request`クラスではなく、`LoginRequest`というカスタムリクエストが使用されています。このリクエストには、認証に必要なバリデーションルールや認証処理のロジックが含まれています

2. **認証処理 (`authenticate`)**  
   - `LoginRequest`の`authenticate`メソッドで、メールアドレスとパスワードの組み合わせが正しいかをチェックします。認証に失敗した場合は、エラーメッセージを返します

3. **セッションの再生成**  
   - 認証に成功すると、セッションを再生成してセッション固定攻撃（Session Fixation Attack）を防ぎます

4. **リダイレクト**  
   - 最後に、ユーザーがアクセスを試みたページ（またはデフォルトの`dashboard`）にリダイレクトします


#### LoginRequestについて

`LoginRequest`は、Laravelの`FormRequest`を拡張したクラスです。このクラスでは、ログインフォームのバリデーションルールや、認証処理のロジックをひとまとめにしています。


##### バリデーションルール

以下のルールで、ログインフォームから送信されたデータが正しい形式であることを検証します。

```php
public function rules(): array
{
    return [
        'email' => ['required', 'string', 'email'],
        'password' => ['required', 'string'],
    ];
}
```

##### 認証処理

`authenticate`メソッドでは、次の処理が行われます。

1. **レート制限の確認**  
   - ログイン試行回数が一定数を超えている場合、`Lockout`イベントを発行し、エラーをスローします

2. **認証試行**  
   - `Auth::attempt`を使用して、メールアドレスとパスワードの組み合わせをチェックします。失敗した場合、試行回数を増加させ、エラーメッセージを返します

3. **試行回数のリセット**  
   - 認証成功時には、試行回数をリセットします

```php
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アドレスやメールアドレスを基にスロットリングを実現しています。

```php
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` ビューを返すだけです。以下が対応するコントローラーのコードです。

```php
public function create(): View
{
    return view('auth.forgot-password');
}
```

このフォームでは、ユーザーが登録済みの **メールアドレス** を入力し、パスワードリセットリンクをリクエストすることができます。

### パスワードリセットリンクリクエストの処理

次に、`POST /forgot-password` のリクエストでは、パスワードリセットリンクの送信処理が実行されます。対応するコントローラーのコードは以下のとおりです。

```php
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)]);
}
```

#### 処理の流れ

1. **バリデーション**  
   - 入力されたメールアドレスが必須で、かつ正しい形式であることをチェックします

2. **パスワードリセットリンクの送信**  
   - `Password::sendResetLink` メソッドを使用して、入力されたメールアドレス宛にリセットリンクを送信します。このメソッドは以下のような2つの状態を返します：
     - **`RESET_LINK_SENT`**: リセットリンクの送信に成功  
     - **`RESET_LINK_FAILED`**: リセットリンクの送信に失敗  

3. **レスポンスの生成**  
   - リセットリンクの送信に成功した場合は、`status` メッセージをセッションに保存して元の画面にリダイレクトします
   - 失敗した場合は、入力されたメールアドレスを保持しつつ、エラーメッセージを表示します

#### メールの送信について

`Password::sendResetLink` を使って、入力されたメールアドレス宛にリセットリンクを送信します。この処理では、Laravelの通知（**`Notification`**）システムを利用してメールの送信が行われます。通知内容はカスタマイズ可能です


## reset-passwordルートでのパスワードリセット処理を読み解く

`reset-password`ルートでは、次の2つの処理が提供されています。

- **`GET /reset-password/{token}`**: パスワードリセットフォームを表示  
- **`POST /reset-password`**: パスワードリセット処理を実行  

それぞれの処理について詳しく見ていきます。

### パスワードリセットフォームの表示

`GET /reset-password/{token}` のリクエストでは、パスワードリセット用のフォームを表示します。この処理では、`auth.reset-password` ビューが返されます。

対応するコントローラーのコードは以下の通りです。

```php
public function create(Request $request): View
{
    return view('auth.reset-password', ['request' => $request]);
}
```

ここでは、トークンやリクエスト情報をビューに渡しています。`auth.reset-password` ビューには、新しいパスワードを入力するためのフォームが含まれています。

### パスワードリセット処理

次に、`POST /reset-password` のリクエストでは、パスワードリセット処理が実行されます。この処理では、送信されたデータをもとにパスワードが更新されます。

対応するコントローラーのコードは以下の通りです。

```php
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)]);
}
```

#### 処理の流れ

1. **バリデーション**  
   - トークン、メールアドレス、パスワードが正しい形式で送信されていることを確認します

2. **パスワードリセット処理**  
   - `Password::reset` メソッドを使用して、ユーザーのパスワードをリセットします。このメソッドには、コールバックとしてパスワード更新処理が渡されます
   - 更新されたパスワードは、`Hash::make` を使って安全にハッシュ化されます

3. **イベントの発行**  
   - パスワードがリセットされると、`PasswordReset` イベントが自動的に発行されます。このイベントを利用して、通知の送信やログ記録など、追加の処理を実装することができます

4. **レスポンスの生成**  
   - パスワードリセットが成功した場合は、`login` ページにリダイレクトし、ステータスメッセージをセッションに保存します
   - リセットが失敗した場合は、入力したメールアドレスを保持しつつ、エラーメッセージを表示します

#### コールバック内の処理

`Password::reset` メソッドのコールバック部分では、次のような操作が行われます。

- **パスワードの更新**  
   ユーザーモデルに新しいパスワードを設定し、保存します
- **リメンバートークンの更新**  
   ユーザーがログイン状態を保持するためのトークンを再生成します
- **イベントの発行**  
   `PasswordReset` イベントを発行して、他の処理（通知やログ記録）をトリガーします。

以下がコールバック部分のコードです。

```php
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の認証機能を学ぶ手助けになれば嬉しいです。