94dikshaverma / laravel-login-tracker
Automatic login activity tracking, device fingerprinting, and new-device alerts for Laravel applications.
Package info
github.com/94dikshaverma/laravel-login-tracker
pkg:composer/94dikshaverma/laravel-login-tracker
Requires
- php: ^8.1
- illuminate/auth: ^10.0|^11.0|^12.0
- illuminate/console: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/notifications: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- jenssegers/agent: ^2.6
Requires (Dev)
- laravel/pint: ^1.16
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.34|^3.0
- pestphp/pest-plugin-laravel: ^2.4|^3.0
- phpstan/phpstan: ^1.11
README
Automatic login activity tracking, device fingerprinting, and new-device alerts for Laravel applications — no listeners to write yourself.
Features
- Records every
Login,Logout, andFailedauth event automatically — nothing to wire up. - Captures IP address (proxy-aware), device type, browser, browser version, and platform.
HasLoginActivitytrait for querying a user's login history like any Eloquent relation.- Detects logins from a new device and sends a queued notification.
- Configurable retention with a
login-tracker:prunecommand (andmodel:prunesupport). - Never stores passwords. IP anonymization and configurable failed-attempt storage for privacy/GDPR.
Requirements
- PHP 8.1+
- Laravel 10.x, 11.x, or 12.x
Installation
composer require 94dikshaverma/laravel-login-tracker php artisan migrate
That's it — login activity is now being recorded. The migration is loaded automatically; publish it only if you want to customize the schema.
Usage
Add the trait to your User model:
use LoginTracker\Concerns\HasLoginActivity; class User extends Authenticatable { use HasLoginActivity; }
Query login history:
$user->loginActivities; // newest first $user->lastLoginAt(); $user->lastLoginIp(); LoginActivity::failed()->fromIp('203.0.113.4')->get(); LoginActivity::successful()->between(now()->subWeek(), now())->get();
Configuration
Publish the config file:
php artisan vendor:publish --tag=login-tracker-config
Key options in config/login-tracker.php:
| Key | Description |
|---|---|
events.* |
Enable/disable tracking for login, logout, failed |
store_unknown_failed_attempts |
Store failed logins for emails that don't match a user (user_id will be null) |
new_device_detection.strategy |
user_agent (default) or user_agent_and_ip_subnet |
new_device_detection.skip_first_login |
Don't alert on a user's first-ever login (default true) |
notifications.class |
Override the notification class |
notifications.channels |
Notification channels (default ['mail']) |
geoip.driver |
null (no location), http (free IP lookup), or custom GeoIpDriver class |
geoip.http.* |
HTTP GeoIP endpoint / timeout / response keys (default: ip-api.com) |
privacy.anonymize_ip |
Zero the last IP octet before storing |
write_mode |
sync or queue |
retention_days |
Used by the prune command (default 365) |
Country & city (GeoIP)
By default the package uses the free http driver (ip-api.com) for public IPs only. Loopback/private IPs stay null (normal on local php artisan serve / Docker: 127.0.0.1).
LOGIN_TRACKER_GEOIP_DRIVER=http # LOGIN_TRACKER_GEOIP_DRIVER=null # disable lookups
Publish config if you need a different provider:
php artisan vendor:publish --tag=login-tracker-config
Identifier
- Login / logout: user email (or username) is stored in
identifier - Failed: attempted email/username from credentials (never the password)
Local development with 127.0.0.1 will always show country/city as null — that is expected.
Customizing the notification
Publish and edit the translation strings:
php artisan vendor:publish --tag=login-tracker-translations
Or swap the notification class entirely via notifications.class in the config — it must extend Illuminate\Notifications\Notification and accept a LoginTracker\Models\LoginActivity in its constructor.
Pruning old records
php artisan login-tracker:prune php artisan login-tracker:prune --days=90
LoginActivity also implements MassPrunable, so php artisan model:prune works if you schedule it.
Privacy & GDPR
- Passwords are never recorded.
session_idis stored as a SHA-256 hash, never the raw session ID.- Set
privacy.anonymize_iptotrueto zero the last IPv4 octet (or trailing IPv6 groups) before storing. - Set
store_unknown_failed_attemptstofalseto avoid persisting attacker-supplied email addresses for accounts that don't exist. - Login activity rows reference
user_idwith a foreign key; delete them (via a model event, observer, oronDelete('cascade')migration edit) when a user is deleted, per your app's data-retention policy. - Review whether recording IP/device data requires a documented lawful basis (e.g. legitimate interest for account security) under your applicable privacy regulation.
Testing
composer test
composer analyse
composer format
License
MIT