Laravel ログイン履歴・ログアウト履歴をDBに記録する

Laravel ログイン履歴・ログアウト履歴をDBに記録する-アイキャッチ PHP

Laravelでログイン履歴・ログアウト履歴をDBに記録する方法をご紹介します。

「Laravel Breeze」という認証機能をインストールしている場合の手順になります。
自分で設定するときに試行錯誤したため、備忘録です😉

事前準備

1. マイグレーションを実行して、DBを作成します。


php artisan migrate


 

データーベースとテーブルが作成されます。


 

2. ログイン履歴・ログアウト履歴を記録用のテーブルを作成するために、
  マイグレーションファイルを作成します。


php artisan make:migration create_user_login_logs_table


 

3. 「手順2」で作成したマイグレーションファイルに、コードを追記します。


<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('user_login_logs', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->string('email')->nullable();
            $table->timestamp('login_at')->nullable();
            $table->timestamp('logout_at')->nullable();
            $table->string('ip_address')->nullable();
            $table->string('status')->nullable();
            $table->string('user_agent')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('user_login_logs');
    }
};

 

4. マイグレーションを実行します。


php artisan migrate


 

5. テーブルが作成できました。


 

6. 「構造」→ 「user_id」で「変更」をクリックします。


 

7. 「Null」にチェックをいれて、保存します。


 
 

アカウントを2つ作成しておく

テスト用として「users」テーブルに、アカウントを2つ作成します。


 

私は 「http://localhost/testlaravel/public/register」にアクセスして、
アカウントを作成しました。


 
 

イベントとリスナーを生成する

1. ファイルを5つ生成するために、コードを上から順番に実行します。


php artisan make:event UserLoggedIn
php artisan make:event UserLoggedOut
php artisan make:listener LogUserLogin --event=UserLoggedIn
php artisan make:listener LogUserLogout --event=UserLoggedOut
php artisan make:listener LogFailedLogin


 

2. 「手順1」で作成したファイルを編集していきます。
  use文の追加も忘れずに!

① app\Events\UserLoggedIn.php


<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\User;

class UserLoggedIn
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $user;
    public $loginSuccess;
    public $email;

    public function __construct(User $user, $loginSuccess, $email)
    {
        $this->user = $user;
        $this->loginSuccess = $loginSuccess;
        $this->email = $email;
    }

    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('channel-name'),
        ];
    }
}

 

② app\Events\UserLoggedOut.php


<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\User;

class UserLoggedOut
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $user;
    public $email;

    public function __construct(User $user, $email)
    {
        $this->user = $user;
        $this->email = $email;
    }

    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('channel-name'),
        ];
    }
}

 

③ app\Listeners\LogUserLogin.php


<?php

namespace App\Listeners;

use App\Events\UserLoggedIn;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;

class LogUserLogin
{
    public function __construct()
    {
        //
    }

    public function handle(UserLoggedIn $event): void
    {
        $user = $event->user;
        $loginSuccess = $event->loginSuccess;
        $email = $event->email;
        $userAgent = Request::header('User-Agent');
        $ipAddress = Request::ip();

        DB::table('user_login_logs')->insert([
            'user_id' => $user->id,
            'email' => $email,
            'login_at' => now(),
            'ip_address' => $ipAddress,
            'user_agent' => $userAgent,
            'status' => $loginSuccess ? 'success' : 'failed',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }
}

 

④ app\Listeners\LogUserLogout.php


<?php

namespace App\Listeners;

use App\Events\UserLoggedOut;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;

class LogUserLogout
{
    public function __construct()
    {
        //
    }

    public function handle(UserLoggedOut $event): void
    {
        $user = $event->user;
        $email = $event->email;

        $userAgent = Request::header('User-Agent');
        $ipAddress = Request::ip();

        DB::table('user_login_logs')->insert([
            'user_id' => $user->id,
            'email' => $email,
            'logout_at' => now(),
            'ip_address' => $ipAddress,
            'user_agent' => $userAgent,
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }
}

 

⑤ app\Listeners\LogFailedLogin.php


<?php

namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;
use App\Models\User;

class LogFailedLogin
{
    public function __construct()
    {
        //
    }

    public function handle(object $event): void
    {
        // ログイン試行の失敗情報を取得
        $credentials = $event->credentials;
        $username = $credentials['email'];

        $userAgent = Request::header('User-Agent');
        $ipAddress = Request::ip();

        // 既存ユーザーの場合はユーザーIDを取得、それ以外はnull
        $user = User::where('email', $username)->first();
        $userId = $user ? $user->id : null;

        DB::table('user_login_logs')->insert([
            'user_id' => $userId,
            'email' => $username,
            'login_at' => now(),
            'ip_address' => $ipAddress,
            'user_agent' => $userAgent,
            'status' => 'failed',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }
}

 
 

EventServiceProvider.php 編集

app\Providers\EventServiceProvider.php


<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use JeroenNoten\LaravelAdminLte\Events\BuildingMenu;
use App\Events\UserLoggedIn;
use App\Events\UserLoggedOut;
use App\Listeners\LogUserLogin;
use App\Listeners\LogUserLogout;
use Illuminate\Auth\Events\Failed;
use App\Listeners\LogFailedLogin;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        UserLoggedIn::class => [
            LogUserLogin::class,
        ],
        UserLoggedOut::class => [
            LogUserLogout::class,
        ],
        Failed::class => [
            LogFailedLogin::class,
        ],
    ];

    public function boot(): void
    {
        //
    }

    public function shouldDiscoverEvents(): bool
    {
        return false;
    }
}

 
 

AuthenticatedSessionController.php 編集

app\Http\Controllers\Auth\AuthenticatedSessionController.php

このファイルは「Laravel Breeze」という認証機能をインストールしている場合に、
存在するファイルです。


<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
use App\Events\UserLoggedIn;
use App\Events\UserLoggedOut;
use App\Listeners\LogUserLogin;
use App\Listeners\LogUserLogout;

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

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

        $user = $request->user();
        $email = $request->input('email');

        event(new UserLoggedIn($user, true, $email)); // ログイン成功
        $request->session()->regenerate();
        return redirect()->intended(RouteServiceProvider::HOME);
    }

    public function destroy(Request $request): RedirectResponse
    {
        $user = $request->user();
        $email = $user->email;

        Auth::guard('web')->logout();

        // ユーザーがログアウトしたことをイベントとして発火
        event(new UserLoggedOut($user, $email));

        $request->session()->invalidate();
        $request->session()->regenerateToken();
        return redirect('/');
    }
}

 
 

動作確認

ログイン成功時の動作

1. 「test1@test.com」でログインします。


 

2. DBにログインした日時などの情報が保存されます。
  ログインが成功した場合は「status」に「success」と表示されます。


 

3. ログアウトすると、ログアウトした日時が保存されます。
  ログアウト時の「status」は NULL と表示されます。

  「http://localhost」のアクセスの場合は「ip_address」に「::1」と表示されるようです。
  レンタルサーバーなどのアクセスの場合は、通常のIPアドレスが取得できました。  


 

ログイン失敗時の動作①

1. 「users」テーブルに存在するアカウントでパスワードを間違えて、ログイン失敗した場合


 


 

2. DBにログインが失敗した日時などの情報が保存されます。
  ログインが失敗した場合は「status」に「failed」と表示されます。


 

ログイン失敗時の動作②

1. 「users」テーブルに存在しないアカウントでログインしようとした場合


 


 

2. DBにログインが失敗した日時などの情報が保存されます。
  ログインが失敗した場合は「status」に「failed」と表示されます。

  「user_id」に NULL と表示されるので、
  「users」テーブルに存在しないメールアドレスであることが分かります。


 

環境:Laravel 10
この記事がお役に立ちますと幸いです。
 

【Laravel】Livewireが動かないときの対処方法:備忘録[図解]
...

 

「Laravelの教科書 バージョン10対応」

最新Laravel 10の機能やアップデート内容を網羅しながら、スキルアップを図る!
「オールカラー」解説で、初心者の方でも迷わずに学ぶことができます。
コードだけで無く「なぜそうなるか」を丁寧に説明してくれます。オススメの1冊です👌


広告
PHP
kirinote.com