Search by

jeffersongoncalves / laravel-visitor-fingerprint

jeffersongoncalves

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

Statistics

Installs: 467

Dependents: 3

Suggesters: 0

Stars: 6

Open Issues: 0

1.0.4 2026-09-13 01:09 UTC

This package is auto-updated.

Last update: 2026-09-13 01:09:35 UTC


README

Laravel Visitor Fingerprint

Laravel Visitor Fingerprint

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads License

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.