94dikshaverma/laravel-login-tracker

Automatic login activity tracking, device fingerprinting, and new-device alerts for Laravel applications.

Maintainers

Package info

github.com/94dikshaverma/laravel-login-tracker

pkg:composer/94dikshaverma/laravel-login-tracker

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-08-06 11:53 UTC

This package is auto-updated.

Last update: 2026-08-06 12:56:12 UTC


README

Automatic login activity tracking, device fingerprinting, and new-device alerts for Laravel applications — no listeners to write yourself.

Tests

Features

  • Records every Login, Logout, and Failed auth event automatically — nothing to wire up.
  • Captures IP address (proxy-aware), device type, browser, browser version, and platform.
  • HasLoginActivity trait 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:prune command (and model:prune support).
  • 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_id is stored as a SHA-256 hash, never the raw session ID.
  • Set privacy.anonymize_ip to true to zero the last IPv4 octet (or trailing IPv6 groups) before storing.
  • Set store_unknown_failed_attempts to false to avoid persisting attacker-supplied email addresses for accounts that don't exist.
  • Login activity rows reference user_id with a foreign key; delete them (via a model event, observer, or onDelete('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