Laravel 11で認証機能を0から自作する 【Part.3 ログイン機能】

Laravel 11で認証機能を0から自作する – ログインとログアウト機能の実装

前回の記事では、ユーザー登録機能を実装しました。今回は、登録したユーザーでログインおよびログアウトができるように機能を実装します。

1. ログイン用コントローラクラスの作成

まず、ユーザーがログインするためのコントローラを作成します。このコントローラでは、ログインフォームの表示と、ユーザーの認証処理を行います。

php artisan make:controller Auth/LoginController

app/Http/Controllers/Auth/LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    public function __construct()
    {
        $this->middleware('guest');  // ログインしていないユーザーのみがアクセス可能
    }

    /**
     * ログインフォームを表示する
     *
     * @return \Illuminate\View\View
     */
    public function showLoginForm()
    {
        return view('auth.login');
    }

    /**
     * ユーザーのログイン処理を実行する
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function login(Request $request)
    {
        // 1. リクエストのデータを検証する
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);

        // 2. 認証を試みる
        if (Auth::attempt($credentials, $request->filled('remember'))) {
            // 認証に成功したら、セッションを再生成する
            $request->session()->regenerate();

            // ダッシュボードにリダイレクトする
            return redirect()->intended('dashboard');
        }

        // 認証に失敗した場合は、ログインページにリダイレクトする
        return back()->withErrors([
            'email' => 'ログイン情報が正しくありません。',
        ])->onlyInput('email');
    }
}

ここで、loginメソッドの処理について詳しく見ていきましょう。

  1. リクエストのデータの検証
    ユーザーから送信されたデータ(emailpassword)を検証します。検証にはvalidateメソッドを使用し、指定されたバリデーションルール(requiredおよびemail)に基づいてチェックを行います。
  2. 認証
    Authファサードの機能の一つである、Auth::attempt()メソッドを使用して、ユーザーの認証を試みます。このメソッドは、渡された認証情報($credentials)をもとに、usersテーブル内のユーザーを検索し、認証が成功した場合にはtrueを返します。
  3. セッションの再生成
    認証に成功した場合、$request->session()->regenerate() メソッドを呼び出してセッションIDを再生成します。これにより、セッション固定攻撃(Session Fixation Attack)のリスクを減らし、セキュリティを強化します。
  4. リダイレクト処理
    認証が成功した場合、ユーザーをダッシュボードページ(/dashboard)にリダイレクトします。もし、認証に失敗した場合は、ログインページにリダイレクトし、エラーメッセージを表示します。

また、コンストラクタ内では、※ guestミドルウェアを適用することで、このコントローラの処理の実行を非ログインユーザーのみに限定し、ログイン中のユーザーがログインページ(/login)にアクセスしようとした場合、自動的にダッシュボードページ(/dashboard)にリダイレクトされる仕組みとなっています。

guestミドルウェアの設定はLaravel 11 ではデフォルトで以下で行われています。
vendor/laravel/framework/src/Illuminate/Foundation/Configuration/Middleware.php
https://github.com/laravel/framework/blob/b7c628776754a7d861dd48ff18768e1c1f2d5f5f/src/Illuminate/Foundation/Configuration/Middleware.php#L734

ルーティングでミドルウェアを設定する場合

クラスのコンストラクタで行わなくても、ユーザ登録機能(/register)で行なっているときと同様、後述のルーティングの箇所で設定を行なっても問題ありません。

Route::get('login', [LoginController::class, 'showLoginForm'])->middleware('guest')->name('login');

2. ログアウト用コントローラクラスの作成

次に、ユーザーのログアウト処理を行うためのコントローラを作成します。

php artisan make:controller Auth/LogoutController

app/Http/Controllers/Auth/LogoutController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth; // Authファサードのインポート
use Illuminate\Http\Request;

class LogoutController extends Controller
{
    /**
     * ログアウト処理を実行する
     *
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function logout(Request $request)
    {
        // 1. ユーザーをログアウトさせる
        Auth::logout();

        // 2. セッションを無効にする
        $request->session()->invalidate();

        // 3. 新しいCSRFトークンを再生成する
        $request->session()->regenerateToken();

        // 4. トップページにリダイレクトする
        return redirect('/');
    }
}

logoutメソッドの詳細

  • セッションの無効化
    invalidate(): 現在のセッションを終了し、すべてのセッションデータを削除します。これにより、ユーザーのログアウトが確実に行われ、セッションの再利用が防止されます。
  • CSRFトークンの再生成
    regenerateToken(): セッションを無効化するだけでは、古いCSRFトークンが再利用されるリスクがあります。トークンを再生成することで、トークンの使い回しによる攻撃を防ぎ、セッション無効化後の安全性をさらに高めることができます。

3. ログインフォームのテンプレート作成

ログインフォームを表示するテンプレートを作成します。

resources/views/auth/login.blade.php

@extends('layouts.app')

@section('title', 'ログイン')

@section('content')
<h2 class="text-center mb-4">ログイン</h2>

<form action="{{ route('login') }}" method="POST">
    @csrf
    <div class="mb-3">
        <label class="form-label" for="email">メールアドレス</label>
        <input id="email" class="form-control" name="email" type="email" value="{{ old('email') }}" autofocus />
        @error('email')
        <div class="text-danger">{{ $message }}</div>
        @enderror
    </div>

    <div class="mb-3">
        <label class="form-label" for="password">パスワード</label>
        <input id="password" class="form-control" name="password" type="password" />
        @error('password')
        <div class="text-danger">{{ $message }}</div>
        @enderror
    </div>

    <div class="mb-3 form-check">
        <input id="remember" class="form-check-input" name="remember" type="checkbox" />
        <label class="form-check-label" for="remember">ログイン情報を記憶する</label>
    </div>

    <button class="btn btn-primary w-100" type="submit">ログイン</button>
</form>
@endsection

4. ログイン/ログアウト リンク追加

トップページにログインページへのリンクを追加します。

resources/views/index.blade.php

@extends('layouts.app')

@section('title', 'トップページ')

@section('content')
    <h2 class="text-center mb-4">トップページ</h2>

    @auth
        <p class="text-center">ログイン中: {{ Auth::user()->name }}</p>
    @endauth

    <div class="d-flex justify-content-center">
        <a href="{{ route('register') }}" class="btn btn-outline-primary mx-2">ユーザー登録</a>
        @auth
        <a href="{{ route('dashboard') }}" class="btn btn-primary mx-2">ダッシュボード</a>
        @else
        <a href="{{ route('login') }}" class="btn btn-outline-primary mx-2">ログイン</a>
        @endauth
    </div>
@endsection

ダッシュボードページにログアウトリンクを追加します。

resources/views/dashboard.blade.php

@extends('layouts.app')

@section('title', 'ダッシュボード')

@section('content')
    <h2 class="text-center mb-4">ダッシュボード</h2>
    <p class="text-center">ログイン中: {{ Auth::user()->name }}</p>
    <div class="d-flex justify-content-center">
        <a href="{{ route('home') }}" class="btn btn-outline-secondary mx-2">トップページに戻る</a>
    </div>
    <form class="text-center mt-4" action="{{ route('logout') }}" method="POST" class="d-inline">
        @csrf
        <button type="submit" class="btn btn-danger mx-2">ログアウト</button>
    </form>
@endsection

5. ルーティング設定

ログイン/ログアウトのルーティング設定を行います。

routes/web.php

今回新たに追加したルーティングをこれまでの実装に追記してください。

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HomeController;
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\LogoutController;

// トップページのルート
Route::get('/', [HomeController::class, 'index'])->name('home');

// ダッシュボードのルート(ログインが必要)
Route::get('/dashboard', [HomeController::class, 'dashboard'])->middleware('auth')->name('dashboard');

// ユーザー登録のルート
Route::get('register', [RegisterController::class, 'showRegistrationForm'])->name('register');
Route::post('register', [RegisterController::class, 'register']);

// ログインのルート
Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
Route::post('login', [LoginController::class, 'login']);

// ログアウトのルート
Route::post('logout', [LogoutController::class, 'logout'])->name('logout');

実装が完了したら、実際に/loginへアクセスし、ログインが行えるか確認します。

前回の記事で、ユーザ登録を行ってそのままログインしている状態の場合は、ダッシュボードページが表示されるので、ログアウト/logoutから動作を確認することができます。

Follow me!