jurager / tracker
Track Sanctum tokens in Laravel.
Installs: 465
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/jurager/tracker
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.4
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/queue: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- laravel/sanctum: ^3.0|^4.0
- nesbot/carbon: ^2.55.2|^3.0
Requires (Dev)
- jenssegers/agent: ^2.6
- mockery/mockery: ^1.4
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.5|^11.5
- whichbrowser/parser: ^2.1
README
Track Laravel Sanctum authentication tokens with detailed metadata including IP addresses, user agents, device information, and optional geolocation data.
- IP Geolocation Tracking (country, region, city)
- Device Detection (type, browser, platform)
- Session Management
- Automatic Token Pruning
- Customizable Providers and Parsers
Note
The documentation for this package is currently being written. For now, please refer to this readme for information on the functionality and usage of the package.
- Requirements
- Installation
- Configuration
- Usage
- IP Geolocation
- Custom Providers
- Events
- Testing
- API Reference
- License
Requirements
- PHP: 8.1 or higher
- Laravel: 10.x, 11.x, or 12.x
- Laravel Sanctum: 3.x or 4.x
Installation
Step 1: Install via Composer
composer require jurager/tracker
Step 2: Publish Configuration
php artisan vendor:publish --provider="Jurager\Tracker\TrackerServiceProvider" --tag="config"
Step 3: Run Migrations
php artisan migrate
Step 4: Install a User-Agent Parser
Choose one of the supported parsers:
# Option 1: jenssegers/agent (Recommended) composer require jenssegers/agent # Option 2: whichbrowser/parser composer require whichbrowser/parser
Update config/tracker.php:
'parser' => 'agent', // or 'whichbrowser'
Configuration
Override Sanctum Model
In your AppServiceProvider or AuthServiceProvider:
use Jurager\Tracker\Models\PersonalAccessToken; use Laravel\Sanctum\Sanctum; public function boot(): void { Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); }
Add Trackable Trait to User Model
use Jurager\Tracker\Traits\Trackable; use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, Trackable; }
Setup Auto-Pruning (Optional)
To automatically remove expired tokens, add to app/Console/Kernel.php:
protected function schedule(Schedule $schedule): void { $schedule->command('model:prune')->daily(); }
Configure expiration in config/tracker.php:
'expires' => 90, // Days until tokens expire (0 = never)
Usage
Basic Usage
The package works transparently with Sanctum. Just create tokens as usual:
$user = User::find(1); $token = $user->createToken('mobile-app'); // Token metadata is automatically populated $accessToken = $token->accessToken; echo $accessToken->device; // "iPhone" echo $accessToken->device_type; // "phone" echo $accessToken->platform; // "iOS" echo $accessToken->browser; // "Safari" echo $accessToken->ip; // "192.168.1.1" echo $accessToken->country; // "United States" (if geolocation enabled) echo $accessToken->location; // "New York, New York, United States"
Session Management
use Illuminate\Http\Request; class AuthController extends Controller { // Logout from current device public function logout(Request $request) { $request->user()->logout(); return response()->json(['message' => 'Logged out']); } // Logout from specific device by token ID public function logoutDevice(Request $request, $tokenId) { $request->user()->logout($tokenId); return response()->json(['message' => 'Device logged out']); } // Logout from all other devices public function logoutOthers(Request $request) { $request->user()->logoutOthers(); return response()->json(['message' => 'Other devices logged out']); } // Logout from all devices public function logoutAll(Request $request) { $request->user()->logoutAll(); return response()->json(['message' => 'All devices logged out']); } }
Querying Tokens
// Get all login history $allLogins = $user->logins; // Get recent logins (last 30 days) $recentLogins = $user->recentLogins(30)->get(); // Get active devices (used in last 30 days) $activeDevices = $user->activeDevices(30)->get(); // Filter by device type $mobileLogins = $user->byDevice('mobile')->get(); $desktopLogins = $user->byDevice('desktop')->get(); // Filter by platform $iosLogins = $user->byPlatform('iOS')->get(); $macLogins = $user->byPlatform('macOS')->get(); // Filter by country $usLogins = $user->byCountry('United States')->get();
Token Methods
$token = $user->tokens()->first(); // Check if token is expired if ($token->isExpired()) { $token->revoke(); } // Mark token as used $token->markAsUsed(); // Check if this is the current token if ($token->isCurrent()) { // This is the current token }
IP Geolocation
Built-in Provider: IP-API
Enable IP geolocation in config/tracker.php:
'lookup' => [ 'provider' => 'ip-api', 'timeout' => 1.0, 'retries' => 2, 'environments' => ['production', 'staging'], ],
Now tokens will include geolocation data:
$token = $user->createToken('mobile-app')->accessToken; echo $token->country; // "United States" echo $token->region; // "California" echo $token->city; // "San Francisco" echo $token->location; // "San Francisco, California, United States"
Built-in Provider: IP2Location Lite
- Download IP2Location DB3 database
- Import to your database
- Configure in
config/tracker.php:
'lookup' => [ 'provider' => 'ip2location-lite', 'ip2location' => [ 'ipv4_table' => 'ip2location_db3', 'ipv6_table' => 'ip2location_db3_ipv6', ], ],
Custom Providers
Create a custom IP provider by extending AbstractProvider:
namespace App\IpProviders; use GuzzleHttp\Psr7\Request as GuzzleRequest; use Jurager\Tracker\Providers\AbstractProvider; class MyCustomProvider extends AbstractProvider { public function getRequest(): GuzzleRequest { return new GuzzleRequest( 'GET', "https://api.example.com/lookup/{$this->ip}" ); } public function getCountry(): ?string { return $this->result?->get('country'); } public function getRegion(): ?string { return $this->result?->get('region'); } public function getCity(): ?string { return $this->result?->get('city'); } // Optional: Store additional data public function getCustomData(): array { return [ 'timezone' => $this->result?->get('timezone'), 'isp' => $this->result?->get('isp'), ]; } }
Register in config/tracker.php:
'lookup' => [ 'provider' => 'my-provider', 'custom_providers' => [ 'my-provider' => \App\IpProviders\MyCustomProvider::class, ], ],
Custom Parsers
Create a custom User-Agent parser by extending AbstractParser:
namespace App\Parsers; use Jurager\Tracker\Parsers\AbstractParser; class MyCustomParser extends AbstractParser { protected $parser; protected function parse(): void { // Initialize your parser with $this->userAgent $this->parser = new SomeParserLibrary($this->userAgent); } public function getDevice(): ?string { return $this->parser->getDeviceName(); } public function getDeviceType(): ?string { return $this->parser->getDeviceType(); } public function getPlatform(): ?string { return $this->parser->getOS(); } public function getBrowser(): ?string { return $this->parser->getBrowserName(); } }
Register in config/tracker.php:
'parser' => 'my-parser', 'custom_parsers' => [ 'my-parser' => \App\Parsers\MyCustomParser::class, ],
Events
TokenCreated Event
Listen to token creation events:
namespace App\Listeners; use Jurager\Tracker\Events\TokenCreated; class NotifyUserOfNewLogin { public function handle(TokenCreated $event): void { $token = $event->personalAccessToken; $context = $event->context; // Send notification $token->tokenable->notify(new NewDeviceLogin( device: $token->device, location: $token->location, ip: $context->ip, )); } }
Register in EventServiceProvider:
use Jurager\Tracker\Events\TokenCreated; protected $listen = [ TokenCreated::class => [ NotifyUserOfNewLogin::class, ], ];
LookupFailed Event
Handle IP lookup failures:
use Jurager\Tracker\Events\LookupFailed; protected $listen = [ LookupFailed::class => [ LogIpLookupFailure::class, ], ];
Testing
Run the test suite:
composer test
Run tests with coverage:
composer test:coverage
Writing Tests
use Jurager\Tracker\Models\PersonalAccessToken; use Laravel\Sanctum\Sanctum; class MyTest extends TestCase { protected function setUp(): void { parent::setUp(); Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); } public function test_user_can_logout(): void { $user = User::factory()->create(); $token = $user->createToken('test')->accessToken; $this->actingAs($user); $user->logout(); $this->assertDatabaseMissing('personal_access_tokens', [ 'id' => $token->id, ]); } }
API Reference
Trackable Trait Methods
| Method | Description |
|---|---|
logins() |
Get all login tokens |
recentLogins(int $days = 30) |
Get recent logins within specified days |
activeDevices(int $days = 30) |
Get active devices (used recently) |
byDevice(string $type) |
Filter by device type (desktop, mobile, tablet, phone) |
byPlatform(string $platform) |
Filter by platform (iOS, Android, Windows, macOS, etc.) |
byCountry(string $country) |
Filter by country name |
logout(int|string|null $tokenId = null) |
Logout from current or specific device |
logoutOthers() |
Logout from all other devices |
logoutAll() |
Logout from all devices |
PersonalAccessToken Methods
| Method | Description |
|---|---|
isExpired() |
Check if token is expired based on config |
revoke() |
Delete/revoke the token |
isCurrent() |
Check if this is the current token (via Request::user()) |
markAsUsed() |
Update last_used_at timestamp |
location |
Get formatted location string (accessor) |
License
Open source, licensed under the MIT license.