label84/laravel-auth-log
Laravel Auth Log adds an audit trail for authentication activity in Laravel apps. It records successful logins, logouts, and failed attempts with useful context, making it easy to review and query auth history through a clean Eloquent model and configurable setup.
Install and publish
composer require label84/laravel-auth-log
php artisan vendor:publish --tag="auth-log-config"
php artisan vendor:publish --tag="auth-log-migrations"
php artisan migrate
v1.4.1 is the version to pin if you are on Laravel 13. If you are on Laravel 10–12, ^1.4.0 still resolves, but there is no reason not to take ^1.4.1 — the release is a constraint-only change (see Gotchas).
Where to look first, in this order
config/auth-log.php — this is where the table name, the model class, the events to record, and any guard filtering live. Read it before you write a single query; most "why isn't this logging?" questions are answered here.AuthLog model — scopes, casts, and any relationship back to User.First use case
The highest-value first use case is failed-login auditing, not login history. Login history is nice-to-have; a queryable record of failed attempts per identifier/IP is what you actually reach for during an incident. Get that working, put a retention window on it, and only then build the "last login" UI affordance.
Lean on native events; add capture where they don't fire. The package hooks Laravel's authentication events (Login, Logout, Failed, and typically Lockout/PasswordReset). Anything that goes through Auth::attempt() or a standard guard is recorded for free. Anything that does not — Sanctum personal access tokens, API key auth, social/OAuth callbacks where you call Auth::login() yourself in a controller, magic-link flows — needs either an explicit event(new Login(...)) dispatch or a manual write. Audit your login entry points before assuming coverage; this is the single most common gap in production.
Multi-guard applications. If you run web and an admin guard, verify the guard is recorded per row and that your queries filter on it. Mixing guards in one table is fine, but an admin "last login" panel that reads unfiltered rows will show the wrong activity.
Last-login on the User model. Add a HasMany to AuthLog and a small accessor rather than denormalising a last_login_at column on users. If you do denormalise for query performance, update it from an event listener on the AuthLog model's created hook so both sources stay consistent.
Alerting on failure spikes. Query failures grouped by identifier or IP over a rolling window in a scheduled command, and alert on thresholds. Pair this with Laravel's Lockout event and rate limiter — the log tells you what happened, the limiter stops it.
Retention. Auth logs grow monotonically and are rarely read after 90 days. Use the model's Prunable/MassPrunable support (or a scheduled delete() with a cutoff) from day one. Decide the window with whoever owns your privacy policy, not by default.
Swapping the model. Point auth-log.model at your own subclass of AuthLog to add scopes, casts, a connection override, or an Auditable-style interface. This is the cleanest extension point and avoids forking.
Testing. Event::fake() in a test silently disables the package — you will get passing tests asserting nothing. Use assertDatabaseHas() against the log table, and never fake events in tests that exercise the logging path.
v1.4.1 is a constraints-only release. The single change is Laravel 13 support. No API changes, no deprecations, no migration changes. Upgrade is safe for existing installs. Two practical consequences: (a) if you are on Laravel 13, older 1.4.x tags will fail to resolve — bump to ^1.4.1; (b) the package now claims a wide support matrix (Laravel 10 through 13), which means CI coverage across those versions is thin — if you are an early Laravel 13 adopter, run your own suite against the auth flows rather than trusting the constraint.
Published migrations freeze. Once you publish, future package migration changes will not reach you. Diff the package's database/migrations against yours on every minor bump.
Failed logins may not capture the attempted identifier. Laravel's Failed event carries the credentials array, but whether the package persists the attempted email is worth verifying against your schema. If it does not, you lose the most useful signal (which account was targeted) — attach a small custom listener for this rather than accepting user_id = null rows you cannot pivot on.
IP addresses behind a proxy. Configure TrustProxies correctly or every row will record your load balancer. This bug is invisible until you try to use the data.
Personal data. IP and user agent are personal data under GDPR/CCPA. Include this table in your export and erasure flows, and consider hashing IPs if you only need correlation, not identification. A retention window is not optional here.
Volume and indexes. Bot traffic will hammer the failed-login path. Ensure indexes on (type, created_at) and on the user foreign key; consider a separate DB connection for writes if auth traffic competes with your read path.
Logouts are explicit only. Session expiry and token revocation do not fire Logout. A missing logout row is not evidence the user stayed logged in — do not build session-duration analytics on this table.
Maintenance signal. This is a single-maintainer package with a small surface area. The fast Laravel 13 turnaround is a good sign, but keep the integration thin — a repository or interface between your app code and AuthLog means you can drop the package for a 40-line event listener if it ever stalls.
How can I help you explore Laravel packages today?