centamiv / advanced-fingerprint
Advanced Server-Side and Client-Side Fingerprinting for Laravel
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/centamiv/advanced-fingerprint
Requires
- php: ^8.1
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2025-12-30 15:51:33 UTC
README
Advanced Server-Side and Client-Side Fingerprinting for Laravel Applications.
Introduction
centamiv/advanced-fingerprint is a powerful Laravel package designed to identify and track devices interacting with your application. Unlike simple IP-based tracking, this library combines server-side analysis (HTTP headers, Client Hints) with optional client-side techniques (Canvas Fingerprinting) to create a robust "Device Fingerprint".
This fingerprint allows you to:
- Detect account sharing.
- Prevent session hijacking.
- Implement more granular rate limiting.
- Track unique devices for analytics.
- Block bots that rotate IPs but keep the same browser configuration.
Why "Advanced"?
Standard fingerprinting usually relies on just the User-Agent and IP address. This is easily spoofed and often inaccurate (e.g., all users behind a corporate proxy might share an IP).
This package goes further by:
- Utilizing Client Hints: Leveraging modern
Sec-CH-UAheaders that replace the User-Agent string. - Hybrid Approach: Merging server-side data with a client-side calculated hash (Canvas + Environment) for maximum entropy.
- Anomaly Detection: Automatically detecting if a logged-in user's device signature changes mid-session (a sign of token theft).
- Developer Friendly: Providing simple helpers, facades, and events to integrate deeply into your business logic.
Key Features
- Zero-Config Start: Works out of the box with sensible defaults.
- Configurable Entropy: Choose which signals (IP, UA, Accept-Language, Platform) to include.
- Dual-Layer Identification: Server Hash (fast, immediate) + Client Hash (accurate, delayed).
- Session Protection: Built-in event firing when a signature mismatch occurs.
- Rate Limit Helper: Limit actions per device, not just per IP.
- Blade Directive: One-line integration for client-side fingerprinting script.
Requirements
- PHP: 8.1 or higher
- Laravel: 10.0 or higher
Installation
Install the package via Composer:
composer require centamiv/advanced-fingerprint
The package will automatically register its service provider.
Next, you should publish the configuration file. This is crucial for customizing the fingerprinting logic:
php artisan vendor:publish --tag=fingerprint-config
This will create a config/fingerprint.php file in your application.
Configuration
The configuration file is the heart of the library. It determines how "sensitive" or "unique" your fingerprints are.
Open config/fingerprint.php:
return [ /* |-------------------------------------------------------------------------- | Fingerprint Algorithm |-------------------------------------------------------------------------- | The hashing algorithm to use for generating the signature. | 'sha256' is recommended for a balance of speed and collision resistance. */ 'algo' => 'sha256', /* |-------------------------------------------------------------------------- | Components |-------------------------------------------------------------------------- | List of components to consider for fingerprint calculation. | The more components you add, the more unique (but volatile) the fingerprint will be. */ 'components' => [ // The IP Address. High entropy, but changes on mobile networks. // Set to false if you want to track devices across different networks. 'ip' => true, // The User-Agent string. Standard, but legacy. 'user_agent' => true, // The Accept-Language header. Helps distinguish users in the same region. 'accept_language' => true, // Modern Client Hints (Sec-CH-UA). Highly recommended for Chrome/Edge. 'sec_ch_ua' => true, 'sec_ch_ua_platform' => true, 'sec_ch_ua_mobile' => true, ], /* |-------------------------------------------------------------------------- | Ignore Routes |-------------------------------------------------------------------------- | Routes to exclude from fingerprint injection (optional). */ 'ignore_routes' => [ 'api/health-check', ], /* |-------------------------------------------------------------------------- | Cache & Rate Limiting |-------------------------------------------------------------------------- */ 'cache' => [ 'prefix' => 'device_sig', 'ttl' => 60, // Cache duration in minutes ], /* |-------------------------------------------------------------------------- | Anomaly Detection |-------------------------------------------------------------------------- | Features to detect session hijacking or spoofing. */ 'anomaly_detection' => [ 'enabled' => true, // Fires Centamiv\AdvancedFingerprint\Events\DeviceSignatureChanged 'fire_event' => true, ], ];
Note on configuration
If you set 'ip' => false, the fingerprint will be identical for the same browser on any network. This is useful for "Remember Device" features but bad for rate limiting (collisions). Choose wisely based on your use case.
Core Concepts
Understanding the architecture helps you use the library effectively.
Server-Side Fingerprint
This is generated immediately on every request. It is calculated from HTTP headers and IP. Pros: Available instantly, cannot be blocked by ad-blockers. Cons: Lower entropy (uniqueness).
Client-Side Fingerprint
This is generated by JavaScript relative to the browser (Canvas, Screen properties, Timezone). It is automatically sent asynchronously to the library. Pros: Very high unique entropy (can distinguish identical laptops). Cons: Requires JS, slightly delayed (exists in session after 2nd request).
Basic Usage
Accessing the Fingerprint
The easiest way is to access the fingerprint via the deviceSignature() method in the Request object:
public function index(Request $request) { $signature = $request->deviceSignature(); // The server-side fingerprint (always available) echo $signature->serverHash; // The client-side fingerprint (available after JS sync) if ($signature->clientHash) { echo $signature->clientHash; } }
Using the Facade
use Centamiv\AdvancedFingerprint\Facades\Fingerprint; // Get the current fingerprint object $signature = Fingerprint::current();
Checks
You can check if a fingerprint matches the current request:
if ($request->deviceSignature()->matches('some-stored-hash')) { // It's the same device }
Frontend Integration
To enable the powerful client-side fingerprinting, you must include the script in your layout file.
The Blade Directive
Add the @deviceFingerprintScript directive to your main layout, preferably directly before the closing </body> tag.
<!DOCTYPE html> <html> <head> <title>My App</title> </head> <body> @yield('content') <!-- Injects the fingerprinting script --> @deviceFingerprintScript </body> </html>
It automatically generates the client-side fingerprint and sends it to the server via a POST request automatically handled by the library.
Customizing the JS
The default client JS logic is at resources/views/vendor/advanced-fingerprint/script.blade.php.
If you want to modyfy the client fingerprint logic itself you need to publish the views:
php artisan vendor:publish --tag=advanced-fingerprint-views
Middleware Usage
The package registers a middleware alias: device.signature.
You can apply it to specific routes to enforce device signature checks:
Route::middleware(['auth', 'device.signature'])->group(function () { Route::get('/dashboard', [DashboardController::class, 'index']); });
Or add it globally to your web middleware group in app/Http/Kernel.php to run on every request:
protected $middlewareGroups = [ 'web' => [ // ... \Centamiv\AdvancedFingerprint\Middleware\CheckDeviceSignature::class, ], ];
Events & Listeners
The library fires Centamiv\AdvancedFingerprint\Events\DeviceSignatureChanged whenever a fingerprint mismatch is detected.
Payload:
$user(Authenticatable): The logged-in user.$oldSignature(string): The signature stored in cache/session.$newSignature(string): The signature detected in the current request.
This is your hook for "Security Alerts".
Cookbook: Real-World Use Cases
Here are extensive examples of how to use this library in production.
1. Intelligent Rate Limiting (Beyond IP)
Standard Laravel throttling limits by IP or User ID. A smart attacker can rotate IPs. With this library, you can limit by Device Fingerprint. Even if they switch IPs (VPN/Proxy), if the browser signals remain the same, they stay blocked.
Implementation:
Create a custom middleware or modify App\Http\Kernel.php.
namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Centamiv\AdvancedFingerprint\DeviceSignatureService; use Symfony\Component\HttpFoundation\Response; class RateLimitByDevice { protected $service; public function __construct(DeviceSignatureService $service) { $this->service = $service; } public function handle(Request $request, Closure $next, int $maxAttempts = 5) { // Generate the fingerprint $fingerprint = $request->deviceSignature(); // Use the service helper to check limits // This checks cache key: device_sig:{hash} if ($this->service->isRateLimited($fingerprint->serverHash, $maxAttempts)) { return response()->json([ 'message' => 'Too many requests from this device.', 'device_id' => $fingerprint->serverHash ], 429); } return $next($request); } }
Now apply this middleware to your sensitive routes (e.g., Login, Registration, Content Scraping targets).
2. Session Hijacking Protection
If a user logs in on their laptop, and suddenly a request comes with the same Session Cookie but from a different Browser or OS, it's highly likely their session was stolen (XSS or simplified Cookie theft).
We can listen for the DeviceSignatureChanged event to invalidate the session immediately.
Step 1: Create the Listener
php artisan make:listener RevokeSessionOnHijack --event=\Centamiv\AdvancedFingerprint\Events\DeviceSignatureChanged
Step 2: Implement the logic
// app/Listeners/RevokeSessionOnHijack.php namespace App\Listeners; use Centamiv\AdvancedFingerprint\Events\DeviceSignatureChanged; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Session; class RevokeSessionOnHijack { public function handle(DeviceSignatureChanged $event): void { // Log the security incident Log::warning('Session Hijacking Detected', [ 'user_id' => $event->user->id, 'old_sig' => $event->oldSignature, 'new_sig' => $event->newSignature, 'ip' => request()->ip(), ]); // Logout the user to force re-authentication Auth::guard('web')->logout(); // Invalidate the session Session::invalidate(); Session::regenerateToken(); // Protection against CSRF // (Optional) Notify the user via Email // Mail::to($event->user)->send(new SecurityAlertMail(...)); abort(403, 'Security violation: Device fingerprint mismatch. Please login again.'); } }
Step 3: Register in EventServiceProvider
In app/Providers/EventServiceProvider.php:
protected $listen = [ \Centamiv\AdvancedFingerprint\Events\DeviceSignatureChanged::class => [ \App\Listeners\RevokeSessionOnHijack::class, ], ];
Ensure anomaly_detection.enabled is true in config/fingerprint.php.
3. Device Content Locking (Paywalls)
Imagine a news site or streaming service that allows "3 free articles per device per month". Using IP is bad (shared Wi-Fi or NAT networking blocks users because they share the same IP). Cookies are cleared by users. Device fingerprinting is persistent.
// app/Http/Controllers/ArticleController.php public function show($id) { $request = request(); $fingerprint = $request->deviceSignature()->serverHash; // Check database for usage $usage = \DB::table('device_usage') ->where('device_hash', $fingerprint) ->where('month', now()->format('Y-m')) ->first(); if ($usage && $usage->view_count >= 3) { // If not subscribed, block if (! $request->user()?->isSubscribed()) { return view('paywall.limit_reached'); } } // Increment usage \DB::table('device_usage')->updateOrInsert( ['device_hash' => $fingerprint, 'month' => now()->format('Y-m')], ['view_count' => \DB::raw('view_count + 1'), 'updated_at' => now()] ); return view('articles.show', ['article' => Article::find($id)]); }
4. Tracking User Devices History
Show users a list of "Devices where you are logged in", similar to Google or Facebook security dashboards.
Step 1: Create a table
php artisan make:migration create_user_devices_table
Schema::create('user_devices', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained(); $table->string('device_fingerprint'); // The hash $table->string('ip_address'); $table->string('user_agent'); $table->timestamp('last_active_at'); $table->timestamps(); });
Step 2: Update the table on login/activity
You can use a middleware for this, or hook into the Login event.
// app/Listeners/LogDeviceOnLogin.php public function handle(Login $event) { $fingerprint = request()->deviceSignature()->serverHash; $device = \App\Models\UserDevice::updateOrCreate( [ 'user_id' => $event->user->id, 'device_fingerprint' => $fingerprint ], [ 'ip_address' => request()->ip(), 'user_agent' => request()->header('User-Agent'), 'last_active_at' => now(), ] ); }
Step 3: Show to User
In your User Profile controller:
public function devices() { $devices = Auth::user()->devices()->orderByDesc('last_active_at')->get(); return view('profile.devices', compact('devices')); }
Deep Dive: Inner Workings
This section details how the fingerprint generation actually works, which is useful for security auditing.
Entropy Sources
Entropy is the measure of randomness or uniqueness. A higher entropy means a lower chance of collision (two different devices having the same fingerprint).
1. Server-Side Entropy (Low - Medium Reliability)
IP Address: Very specific, but changes with networks (Wi-Fi vs 4G).User-Agent: Describes the browser version and OS. Chrome freezes this string to improve privacy, making it less useful.Accept-Language: Can reveal the user's preferred language and region (e.g.,en-US,en;q=0.9,it;q=0.8).Sec-CH-UA-*: Modern replacements for User-Agent. They provide the platform (Windows, Android), architecture, and mobile status.
2. Client-Side Entropy (High Reliability)
Canvas: The script draws a hidden text with specific emoji/styles on a HTML5 Canvas. Different Graphics Cards (GPU) and Drivers render fonts and anti-aliasing slightly differently. By exporting the image data (base64) and hashing it, we get a signature unique to the hardware stack.Hardware Concurrency: Number of logical processors.Color Depth: e.g., 24-bit.Timezone: The user's local timezone.
All these values are hashed using SHA-256 by default.
Canvas Fingerprinting Explained
Canvas fingerprinting is one of the most reliable entropy sources because it exploits the hardware-level differences in how graphics are rendered.
- Instruction: The library instructs the browser to draw a hidden 2D image containing specific text, emojis, and gradients using the HTML5
<canvas>API. - Rendering: The browser uses the operating system's font-rendering engine and the local GPU's anti-aliasing algorithms to draw the image.
- Variation: Subtle differences in graphics drivers, hardware architecture, and OS-level sub-pixel rendering mean that the exact pixel data of the resulting image is unique to that specific hardware/software stack.
- Hashing: This raw pixel data is converted into a hash. Unlike cookies, this identifier is persistent and remains the same even if the user clears their browsing data or uses "Incognito" mode, as it is tied to the machine's physical capabilities.
Testing Your Application
When writing Feature tests for your Laravel application, you might encounter issues if your tests expect real fingerprinting data (especially headers that aren't present in testing environments).
Mocking the Signature
You should mock the DeviceSignatureService in your tests to return a predictable hash.
namespace Tests\Feature; use Tests\TestCase; use Centamiv\AdvancedFingerprint\DeviceSignatureService; class LoginTest extends TestCase { public function test_user_login_with_fingerprint() { // Mock the service $this->mock(DeviceSignatureService::class, function ($mock) { $mock->shouldReceive('generate') ->andReturn('dummy-test-hash-123'); // If you use checkAnomaly, mock that too $mock->shouldReceive('checkAnomaly')->andReturn(null); }); $response = $this->post('/login', [ 'email' => 'test@example.com', 'password' => 'password', ]); $response->assertRedirect('/home'); } }
Testing Middleware
If you use the device.signature middleware, you can disable it in tests if it gets in the way:
$this->withoutMiddleware([\Centamiv\AdvancedFingerprint\Middleware\CheckDeviceSignature::class]);
However, it is better to ensure your request headers in tests mimic a real browser to verify the integration properly.
API Reference
DeviceSignatureService
The core singleton class responsible for logic.
generate(Request $request): string
Generates the server-side SHA-256 hash based on the current configuration.
checkAnomaly(Authenticatable $user, string $currentSignature): void
Compares the current signature with the one cached for the user. Fires DeviceSignatureChanged if different.
isRateLimited(string $signature, int $maxAttempts = 60): bool
Atomic rate limiter using Cache. Returns true if limit exceeded.
DeviceSignature Class
A simple value object holding the state.
public string $serverHash
The hash calculated from HTTP headers.
public ?string $clientHash
The hash calculated from JS (Canvas). Null if not yet synced.
matches(string $hash): bool
Returns true if $serverHash equals the provided string.
Troubleshooting
"The Client Hash is always null"
- Ensure you included
@deviceFingerprintScriptin your layout. - The client hash is sent via AJAX after the page loads. It will not be available in the initial
GETrequest of the page load. It will be available in subsequent requests (AJAX or navigation). - Check your browser console for "Fingerprint Sync Failed" errors.
- Ensure your
route('fingerprint.sync')is accessible and not blocked by other middleware. Only thewebmiddleware group is applied by default.
"My Rate Limiting is blocking everyone on the same Wi-Fi"
- You have
'ip' => truein your config. - If you want to distinguish devices behind a NAT (e.g., Corporate Office), set
'ip' => falseand rely on Header/Canvas entropy. - Warning: Removing IP reduces security against attackers who can simulate headers. It's a trade-off.
Privacy & GDPR
- Device Fingerprinting can be considered "Tracking" under GDPR/ePrivacy.
- You must disclose this in your Privacy Policy.
- If used strictly for "Security" (Fraud detection, Account protection), it is generally a "Legitimate Interest".
- If used for "Analytics" or "Marketing", you likely need Consent (Cookie Banner).
Extending the Library
Custom Generators
Currently, the generator logic is inside DeviceSignatureService.
If you need to add custom logic (e.g., checking specific custom headers passed by your mobile app), you can extend the service and re-bind it.
// App\Services\MyCustomSignatureService.php namespace App\Services; use Centamiv\AdvancedFingerprint\DeviceSignatureService; use Illuminate\Http\Request; class MyCustomSignatureService extends DeviceSignatureService { public function generate(Request $request): string { // Call parent or write complete custom logic $hash = parent::generate($request); // Add a custom header to the mix if ($request->hasHeader('X-My-App-ID')) { $hash = hash('sha256', $hash . $request->header('X-My-App-ID')); } return $hash; } }
In your AppServiceProvider:
$this->app->singleton(\Centamiv\AdvancedFingerprint\DeviceSignatureService::class, \App\Services\MyCustomSignatureService::class);
License
The MIT License (MIT). Please see License File for more information.
Built with ❤️ by Ivan Centamori