A licensing package for Laravel with offline verification, seat management, cryptographic key rotation, and multi-product support.
- Offline verification — PASETO v4 tokens signed with Ed25519, verifiable without a server connection
- Seat-based licensing — control how many devices, users, or instances can use a license
- Full lifecycle management — activation, renewal, grace periods, expiration, suspension
- Multi-product scopes — isolate signing keys per product so a compromise doesn't spread
- Two-level key hierarchy — root CA signs short-lived signing keys; rotate without breaking clients
- Audit trail — append-only log of every license, usage, and key event
- Polymorphic assignment — attach a license to any Eloquent model
- Flexible key management — auto-generation, custom keys, encrypted storage with optional retrieval
- PHP 8.3+
- Laravel 12 or 13
ext-opensslandext-sodium
composer require masterix21/laravel-licensingPublish config and migrations, then migrate:
php artisan vendor:publish --provider="LucaLongo\Licensing\LicensingServiceProvider"
php artisan migrateGenerate your root key and first signing key:
php artisan licensing:keys:make-root
php artisan licensing:keys:issue-signing --kid signing-key-1The root key is encrypted with the passphrase from the
LICENSING_KEY_PASSPHRASEenv variable. If missing, the command will prompt you to set one (unless running with--no-interaction).
Migrations are tested against MySQL 8 and MariaDB 11 in CI. Two points worth knowing if you run into errors on older setups:
- Identifier 1059 errors (
Identifier name '…' is too long): the package already ships explicit short names for the only composite indexes that would exceed MySQL's 64-char limit. If you add custom migrations on top, remember to pass a short alias tomorphs()/index()when the auto-generated name would overflow. - Key length 1071 errors (
Specified key was too long): only relevant on MySQL < 5.7 or MariaDB < 10.2 with InnoDB's old row format. AddSchema::defaultStringLength(191);in yourAppServiceProvider::boot()as per the Laravel docs. This is unrelated to identifier length — it caps the indexed VARCHAR prefix, not the index name.
use LucaLongo\Licensing\Models\License;
$license = License::createWithKey([
'licensable_type' => User::class,
'licensable_id' => $user->id,
'max_usages' => 5,
'expires_at' => now()->addYear(),
]);
// The plain-text key is available right after creation
$licenseKey = $license->license_key; // e.g. "LIC-A3F2-B9K1-C4D8-E5H7"
$license->activate();You can also pass your own key as second argument to createWithKey(), or use the lower-level License::create() with a pre-hashed key via License::hashKey().
use LucaLongo\Licensing\Facades\Licensing;
$usage = Licensing::register(
$license,
'device-fingerprint-hash',
['device_name' => 'MacBook Pro']
);$token = Licensing::issueToken($license, $usage, [
'ttl_days' => 7,
]);if ($license->isUsable()) {
$remainingDays = $license->daysUntilExpiration();
$availableSeats = $license->getAvailableSeats();
}$originalKey = $license->retrieveKey(); // if encrypted storage is enabled
$newKey = $license->regenerateKey(); // old key stops working
$isValid = $license->verifyKey($providedKey);
$license = License::findByKey($licenseKey);Scopes let you manage multiple products with independent signing keys and rotation schedules.
use LucaLongo\Licensing\Models\LicenseScope;
$scope = LicenseScope::create([
'name' => 'ERP System',
'slug' => 'erp-system',
'identifier' => 'com.company.erp',
'key_rotation_days' => 90,
'default_max_usages' => 100,
]);Issue a signing key for this scope:
php artisan licensing:keys:issue-signing --scope erp-system --kid erp-key-2024When you create a license with a license_scope_id, tokens are automatically signed with the scope's key. A compromised key in one scope doesn't affect the others.
Key generation, retrieval, and regeneration are handled by pluggable services:
// config/licensing.php
'services' => [
'key_generator' => \LucaLongo\Licensing\Services\EncryptedLicenseKeyGenerator::class,
'key_retriever' => \LucaLongo\Licensing\Services\EncryptedLicenseKeyRetriever::class,
'key_regenerator' => \LucaLongo\Licensing\Services\EncryptedLicenseKeyRegenerator::class,
],Implement LicenseKeyGeneratorContract (or the retriever/regenerator contracts) to plug in your own logic.
| Package | Description |
|---|---|
| laravel-licensing-client | Client package for validating licenses against a server — offline verification, usage registration, route middleware |
| laravel-licensing-filament-manager | Filament admin panel for license management, usage monitoring, key rotation, and audit trail |
composer test # run tests
composer test-coverage # with coverage
composer analyse # static analysisFull documentation is available in the docs folder.
If this package is useful to you, consider sponsoring its development.
Contributions are welcome. See CONTRIBUTING.md for details.
If you discover a security vulnerability, please email security@example.com instead of using the issue tracker.
MIT. See LICENSE.md.