masterix21/laravel-licensing

Laravel licensing package with polymorphic assignment to any model, activation keys, expirations/renewals, and seat control via LicenseUsage. Supports offline verification with public-keyโ€“signed tokens, a CLI to generate/rotate/revoke keys, and an extensible architecture via config and contracts.

1.0.2 2025-09-17 10:26 UTC

This package is auto-updated.

Last update: 2025-09-18 08:38:19 UTC


README

Latest Version on Packagist GitHub Tests Action Status Total Downloads

Enterprise-grade license management for Laravel applications with offline verification, seat-based licensing, cryptographic security, and multi-product support through License Scopes.

Installation

Install the package via Composer:

composer require masterix21/laravel-licensing

Publish the configuration and migrations:

php artisan vendor:publish --provider="LucaLongo\Licensing\LicensingServiceProvider"

Run the migrations:

php artisan migrate

Generate your root certificate authority key:

php artisan licensing:keys:make-root

Passphrase required: The command encrypts keys using a passphrase stored in the LICENSING_KEY_PASSPHRASE environment variable (configurable via licensing.crypto.keystore.passphrase_env). If the variable is missing, the command will now prompt you to create one unless you run it with --silent/--no-interaction. Set it ahead of time (for example export LICENSING_KEY_PASSPHRASE="your-strong-passphrase") to enable non-interactive automation.

Issue your first signing key:

php artisan licensing:keys:issue-signing --kid signing-key-1

Key Features

  • ๐Ÿ” Offline Verification: PASETO v4 tokens with Ed25519 signatures
  • ๐Ÿช‘ Seat-Based Licensing: Control device/user limits per license
  • ๐Ÿ”„ License Lifecycles: Activation, renewal, grace periods, and expiration
  • ๐Ÿข Multi-Product Support: License Scopes for product/software isolation
  • ๐Ÿ”‘ Two-Level Key Hierarchy: Root CA โ†’ Signing Keys for secure rotation
  • ๐Ÿ“Š Comprehensive Audit Trail: Track all license and usage events
  • ๐ŸŽฏ Flexible Assignment: Polymorphic relationships for any model
  • ๐Ÿ’พ Flexible Key Management: Auto-generation, custom keys, optional retrieval
  • ๐Ÿ”’ Secure Storage: Encrypted key storage with configurable retrieval
  • โšก High Performance: Optimized for enterprise workloads

Quick Start

1. Create and activate a license

use LucaLongo\Licensing\Models\License;
use LucaLongo\Licensing\Models\LicenseScope;

// Method 1: Auto-generate license key
$license = License::createWithKey([
    'licensable_type' => User::class,
    'licensable_id' => $user->id,
    'max_usages' => 5,
    'expires_at' => now()->addYear(),
]);

// The generated key is available immediately after creation
$licenseKey = $license->license_key; // e.g., "LIC-A3F2-B9K1-C4D8-E5H7"

// Method 2: Provide your own license key
$customKey = 'CUSTOM-KEY-12345';
$license = License::createWithKey([
    'licensable_type' => User::class,
    'licensable_id' => $user->id,
    'max_usages' => 5,
    'expires_at' => now()->addYear(),
], $customKey);

// Method 3: Traditional approach with hash only
$activationKey = Str::random(32);
$license = License::create([
    'key_hash' => License::hashKey($activationKey),
    'licensable_type' => User::class,
    'licensable_id' => $user->id,
    'license_scope_id' => $scope->id ?? null,  // Optional scope
    'max_usages' => 5,
    'expires_at' => now()->addYear(),
]);

$license->activate();

2. Register a device

use LucaLongo\Licensing\Facades\Licensing;

$usage = Licensing::register(
    $license, 
    'device-fingerprint-hash', 
    ['device_name' => 'MacBook Pro']
);

3. Issue an offline token

$token = Licensing::issueToken($license, $usage, [
    'ttl_days' => 7,
]);

4. Verify license

if ($license->isUsable()) {
    $remainingDays = $license->daysUntilExpiration();
    $availableSeats = $license->getAvailableSeats();
}

5. Retrieve and manage license keys

// Retrieve the original license key (if stored encrypted)
$originalKey = $license->retrieveKey();

// Check if retrieval is available
if ($license->canRetrieveKey()) {
    $key = $license->retrieveKey();
}

// Regenerate a license key
if ($license->canRegenerateKey()) {
    $newKey = $license->regenerateKey();
    // Old key no longer works, new key is returned
}

// Verify a license key
$isValid = $license->verifyKey($providedKey);

// Find license by key
$license = License::findByKey($licenseKey);

License Key Management

The package provides flexible license key management with three configurable services:

Configuration

// 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,
],

'key_management' => [
    'retrieval_enabled' => true,     // Allow retrieving original keys
    'regeneration_enabled' => true,  // Allow regenerating keys
    'key_prefix' => 'LIC',          // Prefix for generated keys
    'key_separator' => '-',         // Separator for key segments
],

Custom Key Services

You can implement your own key management services:

use LucaLongo\Licensing\Contracts\LicenseKeyGeneratorContract;

class CustomKeyGenerator implements LicenseKeyGeneratorContract
{
    public function generate(?License $license = null): string
    {
        // Your custom key generation logic
        return 'CUSTOM-' . strtoupper(bin2hex(random_bytes(8)));
    }
}

Then register it in the config:

'services' => [
    'key_generator' => \App\Services\CustomKeyGenerator::class,
],

Security Considerations

  • Hashed Storage: Keys are always stored as salted SHA-256 hashes
  • Encrypted Retrieval: Original keys can be stored encrypted (optional)
  • Regeneration History: Previous key hashes are maintained for audit
  • Configurable: Disable retrieval/regeneration for maximum security

Multi-Product Licensing with Scopes

License Scopes enable you to manage multiple products/software with isolated signing keys, preventing key compromise in one product from affecting others.

Create product scopes

use LucaLongo\Licensing\Models\LicenseScope;

// Create scope for your ERP system
$erpScope = LicenseScope::create([
    'name' => 'ERP System',
    'slug' => 'erp-system',
    'identifier' => 'com.company.erp',
    'key_rotation_days' => 90,
    'default_max_usages' => 100,
]);

// Create scope for your mobile app
$mobileScope = LicenseScope::create([
    'name' => 'Mobile App',
    'slug' => 'mobile-app',
    'identifier' => 'com.company.mobile',
    'key_rotation_days' => 30,  // More frequent rotation
    'default_max_usages' => 3,
]);

Issue scoped signing keys

# Issue signing key for ERP system
php artisan licensing:keys:issue-signing --scope erp-system --kid erp-key-2024

# Issue signing key for mobile app
php artisan licensing:keys:issue-signing --scope mobile-app --kid mobile-key-2024

Create scoped licenses

// Create license for ERP system
$erpLicense = License::create([
    'key_hash' => License::hashKey($erpActivationKey),
    'license_scope_id' => $erpScope->id,  // Scoped to ERP
    'licensable_type' => Company::class,
    'licensable_id' => $company->id,
    'max_usages' => 100,
    'expires_at' => now()->addYear(),
]);

// Create license for mobile app
$mobileLicense = License::create([
    'key_hash' => License::hashKey($mobileActivationKey),
    'license_scope_id' => $mobileScope->id,  // Scoped to mobile
    'licensable_type' => User::class,
    'licensable_id' => $user->id,
    'max_usages' => 3,
    'expires_at' => now()->addMonths(6),
]);

// Tokens are automatically signed with the correct scope-specific key
$erpToken = Licensing::issueToken($erpLicense, $erpUsage);
$mobileToken = Licensing::issueToken($mobileLicense, $mobileUsage);

Benefits of License Scopes

  • Key Isolation: Each product has its own signing keys
  • Independent Rotation: Different rotation schedules per product
  • Blast Radius Limitation: Key compromise affects only one product
  • Product-Specific Defaults: Configure max usages, trial days per scope
  • Flexible Management: Programmatic or CLI-based key management

Testing

Run the test suite:

composer test

Run tests with coverage:

composer test-coverage

Static analysis:

composer analyse

Documentation

For comprehensive documentation visit the documentation.

AI Assistant Support

This package includes comprehensive guidelines for AI coding assistants. See AI_GUIDELINES.md for:

  • Claude Code integration patterns
  • ChatGPT/Codex usage examples
  • GitHub Copilot autocomplete triggers
  • Junie configuration and patterns

Requirements

  • PHP 8.3+
  • Laravel 12.0+
  • OpenSSL extension
  • Sodium extension (for PASETO tokens and Ed25519 signatures)

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

Security

If you discover any security-related issues, please email security@example.com instead of using the issue tracker. All security vulnerabilities will be promptly addressed.

License

The MIT License (MIT). Please see License File for more information.

Credits