Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Authentication Log Laravel Package

rappasoft/laravel-authentication-log

Laravel Authentication Log tracks user logins and logouts with IP, browser, location, and device fingerprint, sends alerts for new devices and failed attempts, and adds session management plus suspicious activity detection.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire the package in four commands, then add one trait:

composer require rappasoft/laravel-authentication-log

php artisan vendor:publish --provider="Rappasoft\LaravelAuthenticationLog\LaravelAuthenticationLogServiceProvider" --tag="authentication-log-migrations"
php artisan migration

php artisan vendor:publish --provider="Rappasoft\LaravelAuthenticationLog\LaravelAuthenticationLogServiceProvider" --tag="authentication-log-config"
use Rappasoft\LaravelAuthenticationLog\Traits\AuthenticationLoggable;

class User extends Authenticatable
{
    use AuthenticationLoggable;
}

Check the version matrix before installing. Laravel 13 requires 6.1+; Laravel 11/12 works on 5.x or 6.x; Laravel 10 is stuck on 3.x. Pin explicitly in composer.json if the project is on anything but the current release.

Where to look first, in order:

  1. config/authentication-log.php — this is where 90% of your day-to-day tuning happens: notification classes, rate limits, GeoIP toggle, webhook endpoints, suspicious-activity thresholds, table name/connection.
  2. database/migrations/*_create_authentication_log_table.php — confirms column names (login_at, logout_at, ip_address, user_agent, location, device_id, is_trusted, last_activity_at, is_suspicious, suspicious_reason) so you know what you can query and index.
  3. Rappasoft\LaravelAuthenticationLog\Models\AuthenticationLog — this is the model you'll query from admin screens and reports; it carries the scopes.
  4. The AuthenticationLoggable trait — the surface area of $user->... helpers (getLoginStats(), getDevices(), revokeSession(), detectSuspiciousActivity()).

First use case to build: a "Security" tab on the user profile showing the last 10 logins (IP, device, location, timestamp) plus a "Log out all other devices" button. It exercises the whole package — relation, scopes, session revocation, device fingerprinting — in about 40 lines and immediately demonstrates value to stakeholders.

Implementation Patterns

Security dashboard on the user profile

The relation is authentications(), and it composes with every scope, which is what makes list screens trivial:

$recent = $user->authentications()
    ->successful()
    ->recent(30)          // last 30 days
    ->latest('login_at')
    ->limit(10)
    ->get();

$currentDeviceId = $user->authentications()->active()->latest('last_activity_at')->value('device_id');

Render device_name (settable via updateDeviceName()), location, and a "Trusted" badge from is_trusted. Flag the row matching the current device_id as "This device" so users don't revoke themselves.

Session management as a first-class feature

Don't reinvent "logout other browsers" — the package already has it, and it's the highest-value feature for user trust:

$user->getActiveSessions();              // collection for the UI
$user->getActiveSessionsCount();

$user->revokeSession($sessionId);        // single row
$user->revokeAllOtherSessions($currentDeviceId);  // "log out everywhere else"
$user->revokeAllSessions();              // panic button

Pair the "revoke all other sessions" action with a confirmation and a post-action flash message. Note the current-device ID must come from the logged-in user's active session, not from a request parameter — never let users pass an arbitrary $currentDeviceId.

Trusted devices gating sensitive routes

This is the sleeper feature. Register the alias once and apply it to high-risk groups (billing, API tokens, password change):

// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'trusted.device' => \Rappasoft\LaravelAuthenticationLog\Middleware\RequireTrustedDevice::class,
    ]);
})
Route::middleware(['auth', 'trusted.device'])->group(function () {
    Route::get('/settings/api-tokens', [ApiTokenController::class, 'index']);
});

Because browser upgrades change user agents, the package normalizes the browser version before SHA-256 hashing — so a Chrome minor bump doesn't silently lock users out of trusted routes. Still, provide an escape hatch (email re-verification) in case a user changes machines.

Device trust workflow

Treat trust as an explicit user action, surfaced after a new-device notification:

foreach ($user->getDevices() as $device) {
    // $device->device_id, $device->device_name, $device->is_trusted
}

$user->trustDevice($deviceId);
$user->untrustDevice($deviceId);
$user->updateDeviceName($deviceId, 'Work MacBook');

if ($user->isDeviceTrusted($deviceId)) { /* skip 2FA step-up */ }

A good pattern: after login, if the device is untrusted, redirect to a "Was this you?" interstitial listing the device name and location, with Trust / Not me buttons. "Not me" → revokeAllOtherSessions() plus a password reset prompt.

Admin audit view

The scopes are the whole point — build one filterable table rather than bespoke queries:

$query = AuthenticationLog::query()->forUser($user);

$query->when($request->status === 'failed', fn ($q) => $q->failed());
$query->when($request->ip, fn ($q) => $q->fromIp($request->ip));
$query->when($request->device, fn ($q) => $q->fromDevice($request->device));
$query->when($request->suspicious, fn ($q) => $q->suspicious());
$query->when($request->trusted, fn ($q) => $q->trusted());

$logs = $query->recent(30)->latest('login_at')->paginate(50);

Keep authentications() for user-scoped screens and the AuthenticationLog model directly for cross-user admin/ops screens.

Suspicious activity as a daily digest, not a login-time block

detectSuspiciousActivity() returns structured findings — perfect for a queued daily job rather than inline on every request:

foreach (User::cursor() as $user) {
    foreach ($user->detectSuspiciousActivity() as $finding) {
        // ['type' => 'multiple_failed_logins', 'count' => 5, 'message' => ...]
        // ['type' => 'rapid_location_change', 'countries' => [...], 'message' => ...]
    }
}

Route these into a Slack channel or an internal fraud queue via the built-in webhook config instead of writing transport code yourself:

'webhooks' => [[
    'url' => 'https://hooks.slack.com/services/...',
    'events' => ['login', 'failed', 'new_device', 'suspicious'],
    'headers' => ['Authorization' => 'Bearer your-token'],
]],

Notifications: customize, don't rebuild

Point the config at your own notification classes so new-device and failed-login alerts match your app's brand and locale, and leave the detection logic alone. Same for the mail channel — swap in Slack/SMS by changing the notification's via().

Exports for compliance requests

When legal or a customer asks for access history, use the shipped command rather than writing an export:

php artisan authentication-log:export --format=csv
php artisan authentication-log:export --format=json --path=storage/app/exports/audit.json

Wrap it in your own command if you need to scope to one user, or just drive AuthenticationLog::forUser($user)->get() into a streamed CSV response from a controller.

Gotchas and Tips

Trust proxies or every IP will be 127.0.0.1. The package reads the request IP; behind a load balancer, ALB, or Cloudflare you must configure Laravel's TrustProxies middleware. This is the #1 reported "it logs the wrong IP / location is always unknown" cause — and it silently poisons every downstream feature (rapid-location-change detection, IP filtering, exports).

Version pinning is a real trap. Laravel 10 projects cannot use 5.x or 6.x. Laravel 13 projects need 6.1+. If you're maintaining multiple apps, pin per-project and read the upgrade guide before bumping — v6.0.0 dropped Laravel 10 and required a migration with new columns.

Upgrading from v3 or earlier means an additive migration, not a fresh one. Publish authentication-log-migrations and run migrate; the upgrade migration adds device_id, device_name, is_trusted, last_activity_at, is_suspicious, suspicious_reason only if missing. Do not run migrate:fresh on production to "fix" a schema mismatch — you'll drop the audit trail. Back up the table first and run the migration in a deploy step before the new code is serving traffic.

Duplicate login rows after page refresh or remember-me were the classic complaint. v6 handles this via session-restoration detection, and v6.1.1 fixed a follow-on bug where an active session whose original login_at scrolled out of the restoration window re-logged itself — it now keys off last_activity_at, falling back to login_at for legacy rows with null last_activity_at. If you see duplicates on 6.0.x, upgrade to 6.1.1+; if you see them on 6.1.1+, check whether your own code writes to last_activity_at or bypasses the middleware that keeps it fresh.

GeoIP is optional and fail-soft. Location fields can be null when GeoIP isn't installed or the lookup misses. Never treat a null country as "suspicious", and don't build UI that assumes location is populated. Similarly, rapid_location_change only fires when location data actually exists — don't sell it as universal coverage to stakeholders.

Rate limiting saves you from notification storms. In development, every fresh session is a "new device" — you'll drown in mail. Either disable notifications in local/testing via the config, or point the notification at the log channel locally. Also tune the failed-login rate limit: a password-spraying bot against a real user will otherwise generate one email per attempt.

Combining scopes: mind the ordering. recent(7) takes days (per the docs' "last 7 days" wording) while detectSuspiciousActivity()'s "multiple failed logins" uses an hour window internally. Don't assume the scope argument is hours. Verify against the config thresholds when you build dashboards, or you'll ship a "last 24 hours" panel showing a week.

Index the table before it gets big. You'll query on user_id, login_at, ip_address, and device_id constantly. The shipped migration covers user_id; add composite indexes for (user_id, login_at) and (ip_address) on high-traffic apps, and plan retention — there's no built-in prune command, so schedule one yourself using the scopes:

AuthenticationLog::where('login_at', '<', now()->subYear())->delete();

Keep at least the failed-login and trusted-device history longer than the successful-login history.

Session revocation is best-effort until the session store is checked. Revoking a session row doesn't magically invalidate a session in every driver; verify with your session driver (database sessions are the clean case). Always offer revokeAllSessions() + password reset as the true kill switch, and log that action so it doesn't look like another unexplained logout.

Trusted-device middleware needs a stable session. If it's rejecting users who clearly trusted the device, check that the session cookie (and thus the device ID resolution) is intact — custom domain splits, SESSION_DOMAIN misconfiguration, or an over-aggressive cookie policy will break fingerprint continuity and make every request look like a new device.

Custom table name or connection: set it in the config and be aware the model respects it — but if you write raw SQL in reports, you must interpolate the config value yourself. Don't hardcode authentication_log.

Testing: the package writes rows as a side effect of authentication, so RefreshDatabase plus assertions on the authentication_log table is the pragmatic approach for your feature tests. For UI tests that shouldn't trip real notifications, disable notifications in phpunit.xml environment overrides — otherwise your test suite will queue hundreds of new-device mails.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules
loco/loco
starfederation/datastar-php
yupmin/modern-phystrix
stephenjude/extended-artisan-commands
lumphp/laravel-macroable
lumphp/laravel-collections
lumphp/laravel-contracts
sermepa/sermepa
arkecosystem/fortify