siberfx / authentication-logger
Log user authentication activity (logins, logouts, failed attempts) and notify users about new devices and failed logins.
Requires
- php: ~8.4.0 || ~8.5.0
- illuminate/auth: ^12.0 || ^13.0
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/database: ^12.0 || ^13.0
- illuminate/events: ^12.0 || ^13.0
- illuminate/http: ^12.0 || ^13.0
- illuminate/notifications: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
Requires (Dev)
- orchestra/testbench: ^10.0 || ^11.0
- phpunit/phpunit: ^11.5 || ^12.0
Suggests
- torann/geoip: Required to resolve and store the location of each authentication.
Provides
None
Conflicts
None
Replaces
None
README
Track every authentication on your Laravel application โ successful logins, failed attempts, logouts and "log out other devices" โ together with the IP address, user agent and (optionally) the geographic location. Users are notified when their account is accessed from a new device or when a login attempt fails.
Table of Contents
- Features
- Requirements
- Installation
- Setup
- How It Works
- Configuration
- Usage
- Notifications
- Location Tracking
- Pruning Old Logs
- Database Schema
- Testing
- Upgrading from 1.x
- Changelog
- Credits
- License
Features
- ๐ Logs logins, failed logins, logouts and other-device logouts automatically.
- ๐ New device and failed login notifications (queued, via any notification channel).
- ๐ Optional location lookup via
torann/geoip. - ๐งน Built-in pruning through Laravel's
model:prune. - ๐ Handy history helpers:
lastLoginAt(),previousLoginIp(),latestAuthenticationโฆ - ๐งฉ Fully configurable: table name, model, notification classes, and each listener can be toggled.
- โ Works with any authenticatable model and any guard; models without the trait are ignored.
- ๐ Modern codebase: PHP 8.4+ syntax, Laravel 12+ attributes, full test suite.
Requirements
| Package version | PHP | Laravel |
|---|---|---|
| 2.x | 8.4, 8.5 | 12.x, 13.x |
Installation
Install the package via Composer:
composer require siberfx/authentication-logger
The service provider is registered automatically through package auto-discovery.
Publish and run the migration:
php artisan vendor:publish --tag="auth-logger-migrations"
php artisan migrate
Optionally publish the config file, mail views and translations:
php artisan vendor:publish --tag="auth-logger-config" php artisan vendor:publish --tag="auth-logger-views" php artisan vendor:publish --tag="auth-logger-translations"
| Tag | Publishes to |
|---|---|
auth-logger-config |
config/auth-logger.php |
auth-logger-migrations |
database/migrations/ (timestamped) |
auth-logger-views |
resources/views/vendor/auth-logger/ |
auth-logger-translations |
lang/vendor/auth-logger/ |
Setup
Add the AuthenticationLoggable and Notifiable traits to every authenticatable model you want to track:
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Siberfx\AuthenticationLogger\Traits\AuthenticationLoggable; class User extends Authenticatable { use AuthenticationLoggable; use Notifiable; }
That's it โ no event registration is required.
How It Works
The package listens for Laravel's built-in authentication events:
| Event | Listener | What is recorded |
|---|---|---|
Illuminate\Auth\Events\Login |
LoginListener |
A successful login. Sends the new device notification for unknown devices. |
Illuminate\Auth\Events\Failed |
FailedLoginListener |
A failed attempt against an existing account. Sends the failed login notification. |
Illuminate\Auth\Events\Logout |
LogoutListener |
Sets logout_at on the log of the current device. |
Illuminate\Auth\Events\OtherDeviceLogout |
OtherDeviceLogoutListener |
Marks all other open sessions as cleared_by_user and sets their logout_at. |
A device is identified by the combination of IP address and user agent. Auth::logoutOtherDevices($password) fires the OtherDeviceLogout event.
Each listener can be disabled in the listeners config section.
Configuration
This is the content of the published config/auth-logger.php:
return [ // Table that stores the logs 'table_name' => env('AUTH_LOGGER_TABLE', 'auth_logger'), // Swap for your own subclass of AuthLogger if needed 'model' => \Siberfx\AuthenticationLogger\Models\AuthLogger::class, // Toggle which auth events are recorded 'listeners' => [ 'login' => true, 'failed' => true, 'logout' => true, 'other-device-logout' => true, ], 'notifications' => [ 'new-device' => [ 'enabled' => env('NEW_DEVICE_NOTIFICATION', false), 'location' => env('NEW_DEVICE_NOTIFICATION_LOCATION', false), 'template' => \Siberfx\AuthenticationLogger\Notifications\NewDevice::class, ], 'failed-login' => [ 'enabled' => env('FAILED_LOGIN_NOTIFICATION', true), 'location' => env('FAILED_LOGIN_NOTIFICATION_LOCATION', false), 'template' => \Siberfx\AuthenticationLogger\Notifications\FailedLogin::class, ], ], // Days to keep logs; null keeps them forever 'purge' => env('AUTH_LOGGER_PURGE_DAYS', 60), ];
Environment variables
| Variable | Default | Description |
|---|---|---|
AUTH_LOGGER_TABLE |
auth_logger |
Table name for the logs. |
NEW_DEVICE_NOTIFICATION |
false |
Send the new device notification. |
NEW_DEVICE_NOTIFICATION_LOCATION |
false |
Resolve the location for new device logins. |
FAILED_LOGIN_NOTIFICATION |
true |
Send the failed login notification. |
FAILED_LOGIN_NOTIFICATION_LOCATION |
false |
Resolve the location for failed logins. |
AUTH_LOGGER_PURGE_DAYS |
60 |
Age in days after which logs are pruned. |
Usage
Login history helpers
The AuthenticationLoggable trait adds the following to your model:
$user->authentications; // Collection of all logs, newest first $user->latestAuthentication; // The most recent log (eager-loadable) $user->lastLoginAt(); // ?Carbon โ including failed attempts $user->lastSuccessfulLoginAt(); // ?Carbon $user->lastLoginIp(); // ?string โ including failed attempts $user->lastSuccessfulLoginIp(); // ?string $user->previousLoginAt(); // ?Carbon โ the successful login before the current one $user->previousLoginIp(); // ?string
A common use is showing a "last seen" hint after login:
@if ($at = auth()->user()->previousLoginAt()) Your last login was {{ $at->diffForHumans() }} from {{ auth()->user()->previousLoginIp() }}. @endif
Query scopes
The AuthLogger model ships with query scopes:
use Siberfx\AuthenticationLogger\Models\AuthLogger; AuthLogger::successful()->count(); AuthLogger::failed() ->where('login_at', '>=', now()->subDay()) ->get(); $user->authentications()->fromDevice($ip, $userAgent)->exists();
Eager load the latest log to avoid N+1 queries in admin listings:
User::with('latestAuthentication')->paginate();
Displaying the log
An example Blade table of the current user's sessions:
<table> <thead> <tr> <th>IP Address</th> <th>Browser</th> <th>Location</th> <th>Login At</th> <th>Successful</th> <th>Logout At</th> </tr> </thead> <tbody> @foreach (auth()->user()->authentications()->limit(20)->get() as $log) <tr> <td>{{ $log->ip_address }}</td> <td>{{ $log->user_agent }}</td> <td>{{ $log->location ? ($log->location['city'] ?? '') . ', ' . ($log->location['state'] ?? '') : 'โ' }}</td> <td>{{ $log->login_at?->toDayDateTimeString() }}</td> <td>{{ $log->login_successful ? 'Yes' : 'No' }}</td> <td>{{ $log->logout_at?->toDayDateTimeString() ?? 'โ' }}</td> </tr> @endforeach </tbody> </table>
Tip: for friendlier browser names, parse
user_agentwith a library such asjenssegers/agentorwhichbrowser/parser.
Notifications
| Notification | Sent when | Default |
|---|---|---|
NewDevice |
A user logs in from an IP / user agent that has never logged in successfully before (skipped for users who registered in the last minute). | Disabled |
FailedLogin |
A login attempt against an existing account fails. | Enabled |
Both notifications implement ShouldQueue, so run a queue worker in production.
Channels
Notifications are delivered via mail by default. Define notifyAuthenticationLogVia() on your model to choose other channels:
public function notifyAuthenticationLogVia(): array { return ['mail', 'database']; }
Both notifications implement toArray(), so the database and broadcast channels work out of the box. For Slack, SMS or other channels, install the matching notification channel and add a toSlack() / toVonage() method in a custom notification.
Custom notifications
Extend a built-in notification (or the abstract AuthenticationNotification) and register it as the template in the config:
use Illuminate\Notifications\Messages\MailMessage; use Siberfx\AuthenticationLogger\Notifications\NewDevice; class MyNewDevice extends NewDevice { public function toMail(object $notifiable): MailMessage { return parent::toMail($notifiable) ->action('Review account activity', route('security.index')); } }
// config/auth-logger.php 'template' => \App\Notifications\MyNewDevice::class,
The logged AuthLogger record is available as $this->authLog.
Customising the emails
Publish the views with --tag="auth-logger-views" and edit:
resources/views/vendor/auth-logger/emails/new.blade.phpresources/views/vendor/auth-logger/emails/failed.blade.phpresources/views/vendor/auth-logger/emails/partials/details.blade.php
The views receive $account, $time, $ipAddress, $browser and $location. All strings are translatable through the JSON translation file.
Location Tracking
To record where a login came from, install torann/geoip and publish its config:
composer require torann/geoip
php artisan vendor:publish --provider="Torann\GeoIP\GeoIPServiceProvider" --tag=config
Then enable location per notification type:
NEW_DEVICE_NOTIFICATION_LOCATION=true FAILED_LOGIN_NOTIFICATION_LOCATION=true
The location is stored in the location JSON column. When torann/geoip is not installed, or the lookup fails, the location is skipped silently (errors are reported, never thrown).
Note: when working locally,
geoipreturns its configured default location. The mail templates hide default locations.
Pruning Old Logs
AuthLogger uses Laravel's MassPrunable. Logs older than the purge number of days are removed by the model:prune command. Because model:prune only discovers models inside app/Models, pass the package model explicitly โ e.g. in routes/console.php:
use Illuminate\Support\Facades\Schedule; use Siberfx\AuthenticationLogger\Models\AuthLogger; Schedule::command('model:prune', ['--model' => [AuthLogger::class]])->daily();
Set purge to null to keep logs forever.
Database Schema
| Column | Type | Description |
|---|---|---|
id |
bigint | Primary key. |
authenticatable_type |
string | Morph type of the user model. |
authenticatable_id |
bigint | Morph id of the user. |
ip_address |
string(45) | IPv4 / IPv6 address. |
user_agent |
text | Browser user agent. |
login_at |
timestamp | When the login attempt happened (indexed). |
login_successful |
boolean | Whether the attempt succeeded. |
logout_at |
timestamp | When the session ended. |
cleared_by_user |
boolean | Ended through "log out other devices". |
location |
json | Resolved geo location, if enabled. |
Testing
composer test
The suite runs on Orchestra Testbench with an in-memory SQLite database and is executed in CI against PHP 8.4 / 8.5 and Laravel 12 / 13.
Upgrading from 1.x
- Require PHP 8.4+ and Laravel 12+.
- Update publish tags:
authentication-log-*โauth-logger-*. - In custom notifications, rename
$this->AuthLoggerto$this->authLog. previousLoginAt()/previousLoginIp()now only consider successful logins.- Re-publish the views if you customised them โ they now use
<x-mail::message>and a sharedpartials/detailsview. - If you relied on pruning, schedule
model:prunewith the--modeloption as shown above.
Changelog
Please see CHANGELOG for more information on what has changed recently.
Credits
License
The MIT License (MIT). Please see License File for more information.