harryes / laravel-sentinellog
A vigilant authentication logging and security package for Laravel 10-13.
Package info
github.com/Harish120/laravel-sentinellog
Type:laravel-package
pkg:composer/harryes/laravel-sentinellog
Requires
- php: ^8.2|^8.3|^8.4
- illuminate/auth: ^10.0|^11.0|^12.0|^13.0
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/notifications: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- paragonie/constant_time_encoding: ^2.0||^3.0
Requires (Dev)
- larastan/larastan: ^2.0||^3.0
- orchestra/testbench: ^7.0||^8.0||^9.0||^10.0||^11.0
- pestphp/pest: ^2.0||^3.0||^4.0
- pestphp/pest-plugin-arch: ^2.0||^3.0||^4.0
- pestphp/pest-plugin-laravel: ^2.0||^3.0||^4.0
- pestphp/pest-plugin-type-coverage: ^2.0||^3.0||^4.0
This package is auto-updated.
Last update: 2026-06-23 12:46:16 UTC
README
Laravel SentinelLog is a powerful, all-in-one authentication logging and security package for Laravel. It provides advanced features like device tracking, 2FA, session management, brute force protection, geo-fencing, and SSO support, ensuring security while keeping users informed.
Features
- Authentication Logging: Logs login, logout, and failed attempts.
- Device & Geolocation Tracking: Tracks devices and locations for authentication events.
- Notifications: Alerts for new device logins and failed attempts.
- Two-Factor Authentication (2FA): TOTP-based 2FA with QR code support.
- Session Management: Tracks multiple sessions and detects hijacking.
- Brute Force Protection: Rate-limits login attempts and blocks suspicious IPs.
- Geo-Fencing: Restricts logins to specific countries.
- Single Sign-On (SSO): Token-based SSO for seamless authentication.
- New Location Verification: Detects logins from unrecognised locations and emails the user a verify/deny link, invalidating the session on denial.
Demo Project
Want to see Laravel SentinelLog in action? Check out our demo project:
Laravel SentinelLog Demo
This demo project showcases:
- Complete authentication system with SentinelLog integration
- Real-world implementation of all features
- Best practices for configuration and usage
- Example of custom notifications and event handling
- Interactive UI for testing various security features
To run the demo locally:
git clone https://github.com/Harish120/sentinel-test.git
cd sentinel-test
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate
php artisan db:seed
php artisan serve
Visit http://localhost:8000 to explore the demo.
Installation
Prerequisites
- PHP 8.2 or higher
- Laravel 10.x, 11.x, 12.x, or 13.x
- Composer
Steps
- Install the Package
composer require harryes/laravel-sentinellog
- Publish Configuration
php artisan vendor:publish --tag=sentinel-log-config
2a. (Optional) Publish Views — only if you want to customise the verify/deny confirmation pages:
php artisan vendor:publish --tag=sentinel-log-views
This copies the Blade templates to resources/views/sentinel-log/location/.
Do not publish unless you intend to customise. The package serves the confirmation pages automatically via
loadViewsFrom()— no publishing step is required for them to work. Publishing a copy you never edit will silently shadow future package view updates.
- Run Migrations
php artisan migrate
- Add Trait to User Model
use Harryes\SentinelLog\Traits\NotifiesAuthenticationEvents; class User extends Authenticatable { use NotifiesAuthenticationEvents; protected $fillable = ['two_factor_secret', 'two_factor_enabled_at']; protected $casts = [ 'two_factor_enabled_at' => 'datetime', 'two_factor_secret' => 'encrypted', // encrypts the TOTP secret at rest ]; }
Security: The
encryptedcast uses your application'sAPP_KEYto encrypt the 2FA secret in the database. A database dump will not expose raw TOTP secrets. If you have existing unencrypted secrets, re-generate them after adding the cast — existing codes will stop working until the secret is re-saved through the encryption layer.
Configuration
Edit config/sentinel-log.php to customize the package. Key options:
General Settings
'enabled' => true, 'events' => ['login' => true, 'logout' => true, 'failed' => true], 'table_name' => 'authentication_logs',
Notifications
'new_device' => ['enabled' => true, 'channels' => ['mail'], 'threshold' => 1], 'failed_attempt' => ['enabled' => true, 'channels' => ['mail'], 'threshold' => 3, 'window' => 60], 'session_hijacking' => ['enabled' => true, 'channels' => ['mail']],
To also persist notifications to the database, add 'database' to the channels array for any notification. Your users table must have the notifications table from php artisan notifications:table.
'new_device' => ['enabled' => true, 'channels' => ['mail', 'database']],
Note: The
NewLocationLogindatabase payload storesverification_id(the record's primary key) rather than the raw token, so the verify/deny URLs cannot be reconstructed from the notifications table.
Two-Factor Authentication (2FA)
'two_factor' => [ 'enabled' => false, 'required' => false, // when true, all TwoFactorAuthenticatable users must complete 2FA setup 'middleware' => 'sentinel-log.2fa', 'setup_route' => 'two-factor.setup', ],
enabled— registers thesentinel-log.2famiddleware alias so you can apply it to routesrequired— whentrue, the middleware redirects any user who has not set up 2FA to the setup route; whenfalse(default), only users who have already set up 2FA are prompted to verify
Important: The package does not register a
two-factor.setuproute — you must define it in your own application. If your route has a different name, setsetup_routeto match or use theSENTINEL_LOG_2FA_SETUP_ROUTEenv variable.
Sessions
'sessions' => ['enabled' => true, 'max_active' => 5],
Brute Force Protection
'brute_force' => ['enabled' => true, 'threshold' => 5, 'window' => 15, 'block_duration' => 24],
Geolocation Provider
// Defaults to ipwho.is — free, HTTPS, no API key required. // Override to use your own provider; must return JSON compatible with ipwho.is response format. 'geo_provider_url' => 'https://ipwho.is',
Geo-Fencing
'geo_fencing' => ['enabled' => false, 'allowed_countries' => ['United States', 'Canada']],
SSO
'sso' => ['enabled' => false, 'client_id' => 'default_client', 'token_lifetime' => 24],
New Location Verification
'location_verification' => [ 'enabled' => true, 'channels' => ['mail'], 'token_ttl' => 30, // Minutes until verify/deny links expire 'redirect_after_verify' => '/', 'redirect_after_deny' => '/', ],
Environment Variables
Add these to .env:
SENTINEL_LOG_ENABLED=true SENTINEL_LOG_2FA_ENABLED=true SENTINEL_LOG_2FA_REQUIRED=false SENTINEL_LOG_2FA_SETUP_ROUTE=two-factor.setup SENTINEL_LOG_GEO_PROVIDER_URL=https://ipwho.is SENTINEL_LOG_GEO_FENCING_ENABLED=true SENTINEL_LOG_GEO_FENCING_ALLOWED_COUNTRIES="United States,Canada" SENTINEL_LOG_LOCATION_VERIFICATION_ENABLED=true
Usage Examples
2FA Setup
Generate a 2FA secret and QR code:
use Harryes\SentinelLog\Services\TwoFactorAuthenticationService; $service = new TwoFactorAuthenticationService(); $user->update([ 'two_factor_secret' => $service->generateSecret(), 'two_factor_enabled_at' => now(), ]); $qrCodeUrl = $service->getQrCodeUrl($user->two_factor_secret, $user->email);
Protect routes with 2FA middleware:
Route::middleware('sentinel-log.2fa')->group(function () { Route::get('/dashboard', fn() => 'Protected!'); });
Verify 2FA code:
Route::post('/2fa/verify', function (TwoFactorAuthenticationService $service) { if ($service->verifyCode(auth()->user()->two_factor_secret, request('code'))) { session(['2fa_verified' => true]); return redirect('/dashboard'); } return back()->withErrors(['code' => 'Invalid 2FA code']); });
Failed Login Attempt Notifications
To receive notifications when a user's account hits the failed attempt threshold, implement the NotifiableWithFailedAttempt contract on your User model alongside the NotifiesAuthenticationEvents trait:
use Harryes\SentinelLog\Contracts\NotifiableWithFailedAttempt; use Harryes\SentinelLog\Models\AuthenticationLog; use Harryes\SentinelLog\Traits\NotifiesAuthenticationEvents; class User extends Authenticatable implements NotifiableWithFailedAttempt { use NotifiesAuthenticationEvents; public function notifyFailedAttempt(AuthenticationLog $log): void { $this->notify(new YourFailedAttemptNotification($log)); } }
The method is called automatically by the LogFailedLogin listener once the threshold defined in notifications.failed_attempt.threshold is reached within the configured time window.
SSO Integration
Generate an SSO token:
use Harryes\SentinelLog\Services\SsoAuthenticationService; $ssoService = new SsoAuthenticationService(); $token = $ssoService->generateToken(auth()->user(), 'client_app_1');
Handle SSO login in the client app:
Route::get('/sso/login', fn() => 'Logged in via SSO')->middleware('auth');
Device Recognition
SentinelLog uses a persistent cookie token as the primary device identity signal — the same approach used by GitHub, Google, and Stripe.
How it works:
- On first login from a browser, a cryptographically random 64-character token is generated and stored in a long-lived
sentinel_device_tokencookie (2 years, HttpOnly, SameSite=Lax) - On every subsequent login, the cookie is read and looked up in the login history
- If the token is not found → new device →
NewDeviceLoginnotification sent - If the token is found → recognised device → no notification
Why a cookie and not a header hash?
Header-based hashes that include the IP address break for mobile users (WiFi ↔ cellular), dynamic IPs, and VPN users. The cookie token is stable across all of these. A secondary header hash (User-Agent + Accept-Language + Accept-Encoding) is still stored in device_info alongside the token for forensic reference.
To enable new device notifications, set in config:
'notifications' => [ 'new_device' => ['enabled' => true, 'channels' => ['mail']], ],
Upgrading from a previous version? Existing login records have no
tokenfield indevice_info. Each user will receive a single "new device" email on their first login after the upgrade — after which the cookie is set and recognition is stable.
Session Management
View active sessions:
$sessions = auth()->user()->authenticationLogs()->with('session')->get();
Brute Force & Geo-Fencing
Attempts are automatically rate-limited, and IPs are blocked after exceeding the threshold. Geo-fencing blocks logins from unallowed countries based on config/sentinel-log.php.
New Location Verification
When a user logs in from a city/country they have never used before, SentinelLog automatically sends them a NewLocationLogin notification with two action links:
- Yes, this was me — opens a confirmation page. The user clicks confirm which submits a
POSTrequest, marking the location as trusted and logging alocation_verifiedevent. - No, deny this login — opens a confirmation page showing the location and IP details. The user clicks confirm which submits a
POSTrequest to revoke the session, logging alocation_deniedevent.
Why confirmation pages for both links? Email security scanners (Outlook Safe Links, Apple Mail, Gmail) automatically follow every link in an email on delivery. Without a confirmation step, scanners would silently trust or revoke the session before the user even reads the email.
Both confirmation pages are Blade templates you can customise — see the installation steps above.
The links expire after token_ttl minutes (default 30). No application code changes are required — the check runs inside the LogSuccessfulLogin listener on every login.
To disable the feature:
SENTINEL_LOG_LOCATION_VERIFICATION_ENABLED=false
To prune expired, unactioned verification records:
use Harryes\SentinelLog\Services\LocationVerificationService; app(LocationVerificationService::class)->pruneExpired();
Scheduled Maintenance
SentinelLog accumulates records over time. Add these to your scheduler to keep tables clean:
// routes/console.php (Laravel 11+) or App\Console\Kernel (Laravel 10) use Harryes\SentinelLog\Models\AuthenticationLog; use Harryes\SentinelLog\Services\BruteForceProtectionService; use Harryes\SentinelLog\Services\LocationVerificationService; Schedule::call(fn () => AuthenticationLog::pruneOlderThan()) ->daily() ->name('sentinel-log:prune-auth-logs'); Schedule::call(fn () => app(BruteForceProtectionService::class)->pruneExpired()) ->daily() ->name('sentinel-log:prune-blocked-ips'); Schedule::call(fn () => app(LocationVerificationService::class)->pruneExpired()) ->daily() ->name('sentinel-log:prune-location-verifications');
| Method | What it cleans | Recommended frequency |
|---|---|---|
AuthenticationLog::pruneOlderThan() |
Auth log entries older than prune.days (default 30) |
Daily |
BruteForceProtectionService::pruneExpired() |
Expired IP block records from sentinel_blocked_ips |
Daily |
LocationVerificationService::pruneExpired() |
Expired unactioned location verification tokens | Daily |
You can override the retention period: AuthenticationLog::pruneOlderThan(90) keeps 90 days of history.
Note on IP blocks: A blocked IP is considered inactive once its
expires_attimestamp passes — no record deletion is needed for the block to stop working.pruneExpired()is purely a housekeeping concern.
Contributing
Submit issues or pull requests on GitHub. Feedback is welcome!
License
This package is open-sourced under the MIT License.