mitul456 / license-manager
Laravel client package for the Remote License Server — HMAC-signed activation, validation, grace-period enforcement and update checking.
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.8
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A drop-in Laravel client for the Remote License Server. It activates an installation, validates the license on every request (with an encrypted offline cache and a bounded grace period), gates the application behind activation/expiry screens, and checks for product updates — all over an HMAC-SHA256 signed API.
Requirements
- PHP 8.2+
- Laravel 11 or 12
Installation
This package ships inside the license server repository under
packages/license-manager and is wired in via a Composer path repository. In a
consumer project you would instead require it from your private registry:
composer require mitul456/license-manager
Publish the config (and optionally the views/assets):
php artisan vendor:publish --tag=license-config php artisan vendor:publish --tag=license-views
Configuration
Set the following in your .env:
LICENSE_SERVER_URL=https://licenses.example.com LICENSE_PRODUCT_SLUG=my-product LICENSE_API_SECRET=the-per-product-hmac-secret LICENSE_GRACE_DAYS=7 LICENSE_MODE=strict # strict | passive
api_secret is the per-product HMAC secret shown in the server admin panel.
Every request is signed with the canonical string
{METHOD}\n{PATH}\n{TIMESTAMP}\n{NONCE}\n{RAW_BODY}\n
exactly as the server verifies it.
Gating the application
Protect routes with the license middleware alias:
Route::middleware(['web', 'license'])->group(function () { // ...protected routes });
Or protect the whole app by setting LICENSE_AUTO_GATE=true.
In strict mode an unactivated app is redirected to license.activate and an
invalid one to license.expired. In passive mode the app is never blocked;
you only read state and listen for events.
Programmatic API
use LicenseManager\Facades\License; License::isValid(); // bool License::status(); // 'active', 'expired', ... License::inGrace(); // bool — running on the offline grace period License::info(); // LicenseInfo DTO (masked key, product, dates) License::activate('XXXXX-XXXXX-…'); // activate this domain License::deactivate(); // release this domain License::heartbeat(); // ping + refresh cache License::checkForUpdates('1.2.0'); // ['update_available' => bool, ...]
Helper functions (globally available):
license_is_valid(); license_status(); license_license_key(); license_expires_at(); // ?CarbonImmutable license_grace_until(); // ?CarbonImmutable license_activate($key); license_deactivate();
Artisan commands
| Command | Purpose |
|---|---|
license:activate {key?} |
Activate this installation |
license:deactivate |
Release this installation |
license:check |
Validate now (cache/grace fallback) |
license:status |
Show full license status |
license:heartbeat |
Ping the server, refresh the cache |
license:cache {show|clear} |
Inspect or clear the local cache |
license:check-updates |
Compare installed vs latest version |
The provider also schedules an hourly heartbeat and a 6-hourly re-validation
(configurable / disable via license.schedule.enabled).
Offline behaviour & grace period
- Server reachable, valid → allowed; cache refreshed.
- Server reachable, invalid → blocked (unless passive).
- Server unreachable, cache fresh (within TTL) → allowed from cache.
- Server unreachable, cache stale but within
grace_days→ allowed (grace). - Server unreachable, no cache OR grace exceeded → blocked.
The cache is encrypted at rest with the app key and stores the raw license key separately from the server's masked snapshot.
Events
LicenseActivated, LicenseDeactivated, LicenseValidationFailed,
LicenseExpired, UpdateAvailable — hook them up in your EventServiceProvider.
Testing
composer test
License
MIT