2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FusicAdvent Calendar 2024

Day 7

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

Posted at

こんにちは、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 を使用して guestauth のルートが分けられています。

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

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

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

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

新しいユーザーの登録

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

  1. 名前 (name)
  2. メールアドレス (email)
  3. パスワード (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));
}

処理の流れ

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

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

処理の流れ

  1. カスタムリクエストの使用

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

    • LoginRequestauthenticateメソッドで、メールアドレスとパスワードの組み合わせが正しいかをチェックします。認証に失敗した場合は、エラーメッセージを返します
  3. セッションの再生成

    • 認証に成功すると、セッションを再生成してセッション固定攻撃(Session Fixation Attack)を防ぎます
  4. リダイレクト

    • 最後に、ユーザーがアクセスを試みたページ(またはデフォルトのdashboard)にリダイレクトします

LoginRequestについて

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

バリデーションルール

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

public function rules(): array
{
    return [
        'email' => ['required', 'string', 'email'],
        'password' => ['required', 'string'],
    ];
}
認証処理

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

  1. レート制限の確認

    • ログイン試行回数が一定数を超えている場合、Lockoutイベントを発行し、エラーをスローします
  2. 認証試行

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

    • 認証成功時には、試行回数をリセットします
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)]);
}

処理の流れ

  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 ビューが返されます。

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

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

処理の流れ

  1. バリデーション

    • トークン、メールアドレス、パスワードが正しい形式で送信されていることを確認します
  2. パスワードリセット処理

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

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

    • パスワードリセットが成功した場合は、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の認証機能を学ぶ手助けになれば嬉しいです。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?