技術資料

Laravel 13で既存のテーブルを使ってログイン機能を実装する

作成日:2026.09.02

Laravel 13の既存連載で作成したmembersテーブルを認証用ユーザーとして流用し、Memberモデルと認証プロバイダーを設定します。SQLiteに保存したユーザー情報を使ったメールアドレス・パスワード認証、ログイン済み画面の表示、データベースセッション、ログアウト時のセッション破棄までを実装します。

前回までの記事で、Laravel 13のプロジェクトへSQLiteを接続し、membersテーブルへのデータ登録フォームを作成しました。

今回はその続きとして、既存のmembersテーブルをログイン用ユーザーとして利用します。メールアドレスとパスワードでログインし、ログイン済みユーザーだけがアクセスできる画面を作成します。

また、ログイン状態を保持するセッションの保存先をデータベースにします。認証処理とセッション保存の関係を確認しながら、ログイン成功時のセッション再生成と、ログアウト時のセッション破棄まで実装します。

管理者・一般ユーザーのような権限分けや、パスワードリセット、メールアドレス確認、多要素認証などは扱いません。認可は、ログイン済みかどうかを確認して画面へのアクセスを制限するところまでに絞ります。

前提

今回の対象環境は以下の通りです。

  • OS: Windows 11
  • PHP: 8.3以上
  • Composer
  • Laravel Framework: 13.x
  • SQLite
  • ターミナル: PowerShell

Laravelのプロジェクトを作成し、SQLiteへ接続できる状態から始めます。また、次の記事で作成したmembersテーブルとMemberモデルがあるものとします。

前回までのmembersテーブルは、次のような構成です。

members
├─ id
├─ name
├─ email
├─ created_at
└─ updated_at

このテーブルへ、ログイン時に照合するpasswordカラムを追加します。

認証とセッションの関係

ログイン処理では、ユーザー情報、認証設定、セッションを別のものとして考えると、処理の流れを整理しやすくなります。

要素 今回の役割
membersテーブル 名前、メールアドレス、パスワードのハッシュを保存する
Memberモデル membersテーブルのユーザーをLaravelの認証対象として表す
認証プロバイダー 認証時にどのモデルからユーザーを取得するかを指定する
セッションガード ログイン済みユーザーをリクエスト間で識別する
sessionsテーブル セッションデータをデータベースへ保存する

Laravelの認証機能では、ガードがリクエストごとの認証方法を定義し、プロバイダーが永続化されたユーザーの取得方法を定義します。ログイン状態をセッションへ保存することと、セッションデータをデータベースへ保存することは関連していますが、同じ設定ではありません。

membersテーブルへpasswordカラムを追加する

まず、membersテーブルへpasswordカラムを追加します。プロジェクトのルートディレクトリで、次のコマンドを実行します。

php artisan make:migration add_password_to_members_table --table=members

作成されたMigrationファイルを開き、upメソッドを次のようにします。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('members', function (Blueprint $table): void {
            $table->string('password')->nullable();
        });
    }

    public function down(): void
    {
        Schema::table('members', function (Blueprint $table): void {
            $table->dropColumn('password');
        });
    }
};

今回は既存の説明用レコードを残したままMigrationを実行するため、passwordを一時的にnullableにしています。既存レコードにはまだパスワードがないためです。

本番の会員テーブルで同じ変更を行う場合は、既存ユーザーへのパスワード設定や、パスワード未設定ユーザーの扱いを先に決めてから、必須カラムへ変更するか検討します。

Migrationを実行します。

php artisan migrate

SQLiteのmembersテーブルへpasswordカラムが追加されていることを確認します。

Memberモデルを認証モデルに変更する

Laravelの標準ユーザーモデルは、通常App\Models\Userです。今回は新しいusersテーブルを作らず、既存のMemberモデルを認証対象として使います。

app/Models/Member.phpを次のように変更します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Foundation\Auth\User as Authenticatable;

#[Fillable(['name', 'email', 'password'])]
class Member extends Authenticatable
{
    //
}

Memberクラスの名前から、Eloquentはデフォルトでmembersテーブルを参照します。Authenticatableを継承することで、Laravelの認証機能からユーザーとして扱えるようになります。

また、passwordをModelの保存対象へ追加しています。ただし、入力されたパスワードをそのまま保存してはいけません。登録処理でパスワードを扱う場合は、Hash::make()などでハッシュ化してから保存します。

既存レコードへパスワードを設定する

今回は、確認事項で決めた通り、既存の説明用レコードへTinkerからパスワードを設定します。

まず、Tinkerを起動します。

php artisan tinker

次のコードを実行すると、membersテーブルの先頭のレコードへ、動作確認用のパスワードを設定できます。

use App\Models\Member;
use Illuminate\Support\Facades\Hash;

$member = Member::query()->firstOrFail();
$member->password = Hash::make('local-password');
$member->save();

$member->email;

最後に表示されたメールアドレスを、後ほどログイン画面へ入力します。local-passwordは記事の動作確認用の値なので、実際のサービスでは利用しないでください。

この処理では、パスワードそのものではなく、ハッシュ化された値がデータベースへ保存されます。ログイン時に入力するパスワードは、アプリケーション側でハッシュ化してから送信する必要はありません。Laravelが保存済みのハッシュと照合します。

認証プロバイダーを変更する

次に、Laravelの認証設定で、ログイン対象のモデルをMemberへ変更します。

config/auth.phpguardsprovidersを確認し、次のような構成にします。

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'members',
    ],
],

'providers' => [
    'members' => [
        'driver' => 'eloquent',
        'model' => App\Models\Member::class,
    ],
],

webガードは、membersプロバイダーを使います。membersプロバイダーは、Eloquentを使ってMemberモデルからユーザーを取得します。

プロバイダーのキーは、分かりやすいようにmembersへ変更しました。キーをusersのままにして、参照先のモデルだけをMemberへ変更する構成でも動作しますが、今回のテーブル名とそろえています。

データベースセッションを準備する

Laravelのセッション保存先は、config/session.phpと環境変数で確認できます。今回は、.envへ次の設定を記述します。

SESSION_DRIVER=database

Laravel 13の新規プロジェクトでは、セッションドライバーがデータベースになっている場合があります。値がすでにdatabaseであれば、変更せずそのまま利用できます。

データベースセッションを使うには、セッションデータを保存するsessionsテーブルが必要です。既存のMigrationにsessionsテーブルの定義があるかを確認します。

php artisan migrate:status

sessionsテーブル用のMigrationが存在しない場合は、次のコマンドで作成してからMigrationを実行します。

php artisan make:session-table
php artisan migrate

すでにsessionsテーブルを作成するMigrationがある場合は、同じテーブル用にmake:session-tableを実行しないようにします。重複したMigrationを作成すると、Migration実行時にエラーになる場合があります。

ブラウザから送信されるCookieにはセッションIDが含まれます。Laravelは、そのセッションIDを使ってsessionsテーブルからセッションデータを取得します。認証情報を保存するセッションと、そのセッションデータを保存する場所を分けて考えることがポイントです。

ログイン処理のルートを追加する

ログイン画面、ログイン処理、ログイン済み画面、ログアウト処理のルートを追加します。

routes/web.phpへ次の内容を追加します。

<?php

use App\Http\Controllers\LoginController;
use App\Http\Controllers\MemberController;
use Illuminate\Support\Facades\Route;

Route::middleware('guest')->group(function (): void {
    Route::get('/login', [LoginController::class, 'show'])
        ->name('login');

    Route::post('/login', [LoginController::class, 'authenticate'])
        ->name('login.authenticate');
});

Route::get('/members/dashboard', [MemberController::class, 'dashboard'])
    ->middleware('auth')
    ->name('members.dashboard');

Route::post('/logout', [LoginController::class, 'logout'])
    ->middleware('auth')
    ->name('logout');

guestミドルウェアは、ログインしていない利用者を対象にするルートへ適用しています。ログイン済み画面にはauthミドルウェアを適用し、未ログインの利用者がアクセスできないようにします。

ログアウトはGETではなくPOSTにします。状態を変更する処理なので、BladeのフォームからCSRFトークンを付けて送信します。

LoginControllerを作成する

Controllerを作成します。

php artisan make:controller LoginController

app/Http/Controllers/LoginController.phpを次の内容にします。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;

class LoginController extends Controller
{
    public function show(): View
    {
        return view('auth.login');
    }

    public function authenticate(Request $request): RedirectResponse
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required', 'string'],
        ]);

        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();

            return to_route('members.dashboard');
        }

        return back()->withErrors([
            'email' => 'メールアドレスまたはパスワードが正しくありません。',
        ])->onlyInput('email');
    }

    public function logout(Request $request): RedirectResponse
    {
        Auth::logout();

        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return to_route('login');
    }
}

Auth::attempt()へ渡したemailは、ユーザーを検索する条件として使われます。passwordは、データベースへ保存されたハッシュと照合されます。

ログインに成功したら、$request->session()->regenerate()でセッションIDを再生成します。ログイン前のセッションIDをログイン後も使い続けないようにするためです。

認証に失敗した場合は、メールアドレスが存在するかどうかを推測されにくいように、メールアドレスとパスワードをまとめたエラーメッセージを表示します。

ログアウト時は、まずAuth::logout()で認証状態を解除します。その後、セッションを無効化し、CSRFトークンを再生成しています。

ログイン画面を作成する

ログイン画面のBladeを作成します。resources/views/auth/login.blade.phpへ次の内容を記述します。

<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ログイン</title>
</head>
<body>
    <main>
        <h1>ログイン</h1>

        @if ($errors->any())
            <ul>
                @foreach ($errors->all() as $message)
                    <li>{{ $message }}</li>
                @endforeach
            </ul>
        @endif

        <form method="POST" action="{{ route('login.authenticate') }}">
            @csrf

            <div>
                <label for="email">メールアドレス</label>
                <input
                    id="email"
                    type="email"
                    name="email"
                    value="{{ old('email') }}"
                    autocomplete="email"
                    required
                >
            </div>

            <div>
                <label for="password">パスワード</label>
                <input
                    id="password"
                    type="password"
                    name="password"
                    autocomplete="current-password"
                    required
                >
            </div>

            <button type="submit">ログイン</button>
        </form>
    </main>
</body>
</html>

フォームには@csrfを追加します。パスワードの入力値は、バリデーションエラーが発生しても再表示しません。

ログイン済み画面を作成する

次に、ログイン済みユーザーの情報を表示する画面を作成します。既存のMemberControllerへ、次のメソッドを追加します。

use Illuminate\Http\Request;
use Illuminate\View\View;

public function dashboard(Request $request): View
{
    return view('members.dashboard', [
        'member' => $request->user(),
    ]);
}

authミドルウェアをルートへ適用しているため、このメソッドが呼び出される時点ではログイン済みのユーザーが想定されます。

resources/views/members/dashboard.blade.phpを作成します。

<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>メンバーページ</title>
</head>
<body>
    <main>
        <h1>メンバーページ</h1>

        <p>{{ $member->name }}さん、ログインしました。</p>
        <p>メールアドレス: {{ $member->email }}</p>

        <form method="POST" action="{{ route('logout') }}">
            @csrf
            <button type="submit">ログアウト</button>
        </form>
    </main>
</body>
</html>

名前やメールアドレスは、Bladeの{{ }}でエスケープして出力します。今回はログイン済みかどうかを確認するための画面なので、ユーザーの編集や削除などは実装しません。

動作を確認する

Laravelの開発サーバーを起動します。

php artisan serve

ブラウザでログイン画面を開きます。

http://127.0.0.1:8000/login

Tinkerで確認したメールアドレスと、設定したlocal-passwordを入力します。認証に成功すると、/members/dashboardへリダイレクトされ、メンバー名とメールアドレスが表示されます。

ログインに成功する場合
  • 入力したメールアドレスがmembers.emailに存在する
  • members.passwordにハッシュ化されたパスワードが保存されている
  • config/auth.phpのproviderがMemberを参照している
  • Auth::attempt()のキーとテーブルのカラム名が一致している
未ログインで保護画面へアクセスする

いったんログアウトするか、別のブラウザで次のURLを開きます。

http://127.0.0.1:8000/members/dashboard

未ログインの場合は、authミドルウェアによってログイン画面へリダイレクトされます。

ログアウトする

メンバーページの「ログアウト」をクリックします。ログイン画面へ戻った後、もう一度/members/dashboardを開きます。ログイン状態が破棄されているため、保護画面は表示されません。

sessionsテーブルを確認する

ログイン前後で、SQLiteのsessionsテーブルにセッションデータが保存されていることを確認します。ログアウト後は、現在のセッションが無効化されます。

セッションの有効期限や、古いセッションデータの削除方法は、今回の最小構成では扱いません。運用時には、セッションの有効期間や不要データの整理方法も確認します。

うまく動かない場合

認証プロバイダーの設定でエラーになる

config/auth.phpguards.web.providerと、providersのキーが一致しているか確認します。

guards.web.provider: members
providers.members.model: App\Models\Member

設定を変更した後も古い値が使われる場合は、設定キャッシュを確認します。必要に応じて、次のコマンドで設定キャッシュを削除します。

php artisan config:clear
Base table or view not found: membersになる

membersテーブルが作成されているか、Migrationの実行状況を確認します。

php artisan migrate:status

Memberモデルで別のテーブル名を指定していないか、クラス名とテーブル名の対応も確認します。

passwordカラムがない

add_password_to_members_tableのMigrationを作成しただけで、実行していない可能性があります。次のコマンドを実行します。

php artisan migrate

Migrationを実行済みなのにカラムがない場合は、接続しているSQLiteファイルが、確認しているファイルと同じかを確認します。

正しいパスワードを入力してもログインできない

Tinkerでパスワードを設定したレコードのメールアドレスを使っているか確認します。また、passwordカラムへlocal-passwordのような平文が保存されていないか確認します。

平文を保存した場合、Laravelの認証処理でハッシュと照合できません。TinkerでHash::make()を使って設定し直します。

419になる

ログインフォームとログアウトフォームに@csrfがあるか確認します。セッションCookieが保存されていない場合もCSRF検証に失敗するため、ブラウザのCookie設定とSESSION_DRIVERsessionsテーブルを確認します。

注意点

  • 既存の説明用membersテーブルを認証に流用しているため、実際の会員管理機能としては不十分です。
  • 今回は既存のemailカラムへUNIQUE制約を追加していません。実際の会員管理では、メールアドレスの重複を許可しない設計を検討します。
  • パスワードは必ずハッシュ化して保存し、平文をデータベースやログへ残さないようにします。
  • ログイン成功時にはセッションIDを再生成し、セッション固定攻撃への対策を行います。
  • ログアウト時は認証解除だけでなく、セッション無効化とCSRFトークン再生成も行います。
  • ログイン試行のレート制限、アカウントロック、パスワードリセット、メールアドレス確認は別途設計が必要です。
  • 今回の認可はログイン済み確認だけです。ユーザーごとの所有権や管理者権限は確認していません。
  • migrate:freshはデータベースのテーブルとデータを削除する可能性があるため、既存データがある環境では安易に実行しないでください。
  • SQLiteはローカル開発には使いやすい一方、同時アクセスやバックアップを含む本番環境の要件は別途検討します。

まとめ

今回は、Laravel 13の既存連載で作成したmembersテーブルを認証用ユーザーとして流用しました。passwordカラムを追加し、Tinkerからハッシュ化したパスワードを既存レコードへ設定しています。

Memberモデルを認証モデルとして設定し、config/auth.phpのproviderから取得できるようにしました。ログイン処理ではAuth::attempt()を使い、成功時にセッションIDを再生成します。

セッションドライバーにはdatabaseを指定し、sessionsテーブルへセッションデータを保存します。ログイン済み画面にはauthミドルウェアを適用し、ログアウト時には認証解除、セッション無効化、CSRFトークン再生成を行いました。

今回はログイン済みかどうかの確認までに範囲を絞っています。実際のサービスへ導入する場合は、権限管理、ログイン試行制限、パスワードリセット、メールアドレス確認なども含めて設計する必要があります。

参考資料

この記事を書いた人

※上が私です。

奈良市を拠点に、27年以上の経験を持つフリーランスWebエンジニア、阿部辰也です。

これまで、ECサイトのバックエンド開発や業務効率化システム、公共施設の予約システムなど、多彩なプロジェクトを手がけ、企業様や制作会社様のパートナーとして信頼を築いてまいりました。

【制作会社・企業様向けサポート】
  • 専任エンジニアのいない企業様に対するシステム面の不安を解消
  • 柔軟な契約形態や短納期での対応により、急なニーズにも迅速にサポート
  • システムの企画段階から運用まで、ワンストップでのサービスを提供

Webシステムの開発やサイト改善でお困りの際は、どうぞお気軽にご相談ください。小さな疑問から大規模プロジェクトまで、最適なご提案を心を込めてさせていただきます。

ぜひ、プロフィールWeb制作会社様向け業務案内一般企業様向け業務案内もご覧くださいね。

Laravel 13+Vue.jsで画像・CSVを非同期アップロードする:FormData・ファイル検証・非公開保存

2026.08.31

Laravel 13のBladeに組み込んだVue.jsから、FormDataを使って画像またはCSVを非同期で送信する方法を解説します。JPEG・PNGの画像検証、CSVの文字コード変換と簡易的な内容確認、20MBのサイズ制限、非公開ディスクへの保存、CSRF・バリデーションエラー処理を実装し、fetchを基本にaxiosとの違いも整理します。

JavaScript Laravel PHP Vue.js

Laravel 13の登録フォームにPwned Passwords APIを組み込む:漏洩パスワードの確認と障害時の扱い

2026.08.30

Laravelで、説明用の登録フォームにPwned Passwords APIを組み込みます。SHA-1ハッシュの先頭5文字だけをAPIへ送り、返されたサフィックスをアプリケーション側で照合します。漏洩検出時はフォームエラー、API障害時は警告を表示して登録を続ける構成と、カスタムバリデーションルールとの使い分けを解説します。

Laravel PHP Pwned Passwords API

Laravel 13をApache・XAMPPで動かす:VirtualHost・.htaccess・public_htmlの配置方法

2026.08.27

Laravel 13のプロジェクトをWindowsのXAMPP Apacheで動かすために、VirtualHostでpublicをDocumentRootに指定する方法、.htaccessでpublicへ振り分ける方法、public_htmlへpublicの中身を配置してindex.phpの参照先を変更する方法を解説します。.envやstorageなどを公開しないための注意点、Viteのビルド成果物、ApacheとLaravelのログを使ったトラブルシューティングも整理します。

.htaccess Apache Laravel PHP XAMPP

Laravel 13+Vue.jsで登録・編集フォームを非同期化する:fetchとaxiosを比較

2026.08.26

Laravel 13のBlade画面にVue.jsを組み込み、membersテーブルのnameとemailを登録・編集するフォームを作ります。Vue.jsからLaravelのWebルートへJSONを送信し、CSRFトークン、バリデーションエラー、送信中・通信失敗の状態を扱います。リクエスト送信にはfetchとaxiosを使う方法を比較し、追加パッケージの有無やエラー処理の違いを整理します。

JavaScript Laravel PHP Vue.js

阿部辰也へのお仕事の依頼・お問い合わせ

軽いご相談もお気軽にどうぞ!

個人情報の取り扱いについて *必須 プライバシーポリシーをご確認いただき、同意いただける場合は「同意する」にチェックをしてください。

keyboard_double_arrow_up
TOP