jeffersongoncalves / laravel-visitor-fingerprint
A zero-domain-knowledge visitor-fingerprinting toolkit for Laravel: device/browser/OS detection, IP anonymization, bot detection, GeoIP, VPN/proxy/Tor detection, and GDPR export/erasure
Package info
github.com/jeffersongoncalves/laravel-visitor-fingerprint
pkg:composer/jeffersongoncalves/laravel-visitor-fingerprint
Requires
- php: ^8.3
- illuminate/contracts: ^12.0|^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- geoip2/geoip2: ^3.4
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
Suggests
- geoip2/geoip2: Required to resolve visitor geolocation with the MaxMind GeoIP driver.
Provides
None
Conflicts
None
Replaces
None
README
Laravel Visitor Fingerprint
A zero-domain-knowledge visitor-fingerprinting toolkit for Laravel: given an HTTP request, safely identify device/browser/OS, anonymize/hash the IP, detect bots, resolve GeoIP, detect VPN/proxy/Tor, and support GDPR export/erasure. This package has no concept of "short URL" or "page visit" — those are concerns of the packages that consume it.
Installation
You can install the package via composer:
composer require jeffersongoncalves/laravel-visitor-fingerprint
Optionally publish the config file:
php artisan vendor:publish --tag="visitor-fingerprint-config"
Usage
User agent, bot, language, referer
use JeffersonGoncalves\VisitorFingerprint\Facades\VisitorFingerprint; VisitorFingerprint::deviceType($request->userAgent()); // 'desktop' | 'mobile' | 'tablet' VisitorFingerprint::parseUserAgent($request->userAgent()); // ['browser' => 'Chrome', 'browser_version' => '128.0.0.0', 'operating_system' => 'Windows', 'operating_system_version' => '10.0'] VisitorFingerprint::isBot($request->userAgent()); // bool VisitorFingerprint::preferredLanguage($request->header('Accept-Language')); // 'en-US' VisitorFingerprint::classifyReferer($request->header('Referer'), $request->getHost()); // 'social' | 'search' | 'email' | 'internal' | 'direct'
IP anonymization and hashing
use JeffersonGoncalves\VisitorFingerprint\Support\IpAnonymizer; IpAnonymizer::truncate($request->ip()); // '203.0.113.0' (IPv4 /24) or '2001:0db8:1234::' (IPv6 /48) IpAnonymizer::hash($request->ip()); // salted sha256, safe to store for uniqueness comparisons IpAnonymizer::version($request->ip()); // 4 or 6
GeoIP
use JeffersonGoncalves\VisitorFingerprint\Facades\VisitorFingerprint; $location = VisitorFingerprint::geoLocate($request->ip()); $location->country; $location->city; $location->latitude; $location->longitude; $location->isp; $location->asn; // only populated by the ip_api driver, or the maxmind driver when an ASN database is configured (see below)
Driver is selected via visitor-fingerprint.geoip.driver: headers (trusts CDN-injected geo headers, e.g. Cloudflare/CloudFront — no isp/asn), ip_api (free-tier ip-api.com HTTP lookup, includes isp/asn), or maxmind (local MaxMind database, requires geoip2/geoip2).
The maxmind driver reads two separate MaxMind databases: GeoLite2-City (country/region/city/coordinates) has no ISP/ASN fields at all — those only exist in GeoLite2-ASN. Configure both paths and this package's own geoip:update command keeps them fresh:
php artisan geoip:update
Downloads and installs both editions with your MaxMind license key (visitor-fingerprint.geoip.maxmind_license_key, falls back to plain MAXMIND_LICENSE_KEY). Each edition is independent — a failure on one doesn't block the other from updating, and a failed run never touches the last-known-good file. Schedule it, e.g.:
// routes/console.php Schedule::command('geoip:update')->weeklyOn(1, '02:00');
The ASN database is optional — without it (or with the headers/ip_api driver), isp/asn just stay null, same best-effort behavior as everything else in this package.
VPN / proxy / Tor detection
use JeffersonGoncalves\VisitorFingerprint\Facades\VisitorFingerprint; $threat = VisitorFingerprint::checkThreat($request->ip()); $threat->isVpn; $threat->isProxy; $threat->isTor; $threat->isDatacenter; $threat->confidence;
Driver is selected via visitor-fingerprint.vpn_detection.driver: ip_api or proxycheck. Both are best-effort — any lookup failure yields a "clean" result instead of raising, and results are cached per IP.
GDPR / LGPD export and erasure
PersonalDataExporter is reusable against any consumer's own visit-like Eloquent model — this package has no model of its own:
use JeffersonGoncalves\VisitorFingerprint\Compliance\PersonalDataExporter; $exporter = new PersonalDataExporter(\App\Models\Visit::class, ipHashColumn: 'ip_hash'); $exporter->exportForIp($ip); // rows matching the hashed IP, as arrays $exporter->forgetForIp($ip); // nulls only the PII columns that exist on the model's table
Configuration
// config/visitor-fingerprint.php return [ 'hash_salt' => env('VISITOR_FINGERPRINT_HASH_SALT', config('app.key')), 'geoip' => [ 'driver' => env('VISITOR_FINGERPRINT_GEOIP_DRIVER', 'headers'), 'maxmind_database_path' => env('VISITOR_FINGERPRINT_MAXMIND_DB_PATH', storage_path('app/geoip/GeoLite2-City.mmdb')), 'maxmind_asn_database_path' => env('VISITOR_FINGERPRINT_MAXMIND_ASN_DB_PATH', storage_path('app/geoip/GeoLite2-ASN.mmdb')), 'maxmind_license_key' => env('VISITOR_FINGERPRINT_MAXMIND_LICENSE_KEY', env('MAXMIND_LICENSE_KEY')), ], 'vpn_detection' => [ 'driver' => env('VISITOR_FINGERPRINT_VPN_DRIVER', 'ip_api'), 'proxycheck_api_key' => env('VISITOR_FINGERPRINT_PROXYCHECK_API_KEY'), 'cache_ttl' => env('VISITOR_FINGERPRINT_VPN_CACHE_TTL', 3600), 'timeout' => env('VISITOR_FINGERPRINT_VPN_TIMEOUT', 2.0), ], ];
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email the author instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.
