ozankurt/laravel-modules-licensing

Self-hosted software licensing for Laravel: issue/validate license keys, Ed25519 signed offline keys, seat activations, private Composer gating, and a Core-free client SDK.

Maintainers

Package info

github.com/OzanKurt/KurtModules-Licensing

pkg:composer/ozankurt/laravel-modules-licensing

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-07-21 05:04 UTC

This package is auto-updated.

Last update: 2026-07-21 06:02:56 UTC


README

Self-hosted software licensing for Laravel. Issue and validate license keys, sign offline license files, enforce per-machine seat limits, and gate private Composer downloads — everything you need to sell a premium Laravel/Filament package, running on your own infrastructure.

Part of the KurtModules family. Headless by design, with an optional Filament admin (Filament 3, 4 & 5) and a Core-free client SDK you embed in the package you sell.

Features

  • License keys — human-friendly, crypto-random keys (ABCD-EF23-GH45-JK67). Only a keyed HMAC of the key is stored, never the plaintext.
  • Three policiesperpetual, subscription (time-limited), and updates_window (runs forever, but only downloads releases published before a cutoff — the "license expires, software keeps working" model).
  • Seat activations — per-machine fingerprints with a configurable limit. Re-activating the same machine is idempotent; deactivation frees the seat.
  • Online + offline — a JSON API for live validation, and Ed25519-signed license files that verify entirely offline with only a public key.
  • Composer gating — an HTTP-Basic bridge (email + key) that authorizes Composer downloads against the license, front your private Satis or use the bundled auth_request endpoint.
  • Audit trail — every issue / activate / validate / composer decision is logged.
  • Client SDKKurt\Modules\Licensing\Client\* depends only on illuminate/* (no Core), so you can embed it in the package you sell. Includes an offline grace window so a network blip never bricks a paying customer.

Requirements

Installation

composer require ozankurt/laravel-modules-licensing

Core is not on Packagist yet, so add it as a VCS repository in your app's composer.json:

"repositories": [
    { "type": "vcs", "url": "https://github.com/OzanKurt/KurtModules-Core" }
]

Publish config + migrations and migrate:

php artisan vendor:publish --tag=modules-licensing-config
php artisan vendor:publish --tag=modules-licensing-migrations
php artisan migrate

Generate a signing keypair and add it to .env (keep the signing key on the server only):

php artisan licensing:keygen
# LICENSING_SIGNING_KEY=...   (server only — signs license files)
# LICENSING_PUBLIC_KEY=...    (also embed in the package you sell — verifies them)

Server usage

Define a product

use Kurt\Modules\Licensing\Server\Models\Product;

$product = Product::create([
    'slug' => 'acme-pro',
    'name' => ['en' => 'Acme Pro'],
    'composer_packages' => ['acme/pro'],            // gated for Composer
    'default_policy' => ['type' => 'subscription', 'max_activations' => 3],
]);

Issue a license

use Kurt\Modules\Licensing\Facades\Licensing;

$issued = Licensing::issue($product, [
    'licensee_email' => 'buyer@example.com',
    'expires_at' => now()->addYear(),
]);

$issued->key;      // 'ABCD-EF23-...' — show once, it is not recoverable
$issued->license;  // the persisted License model

…or from the CLI:

php artisan licensing:issue acme-pro buyer@example.com --seats=3 --expires="2027-01-01"

Sign a downloadable license file (offline activation)

$blob = base64_encode(json_encode(Licensing::signFileFor($issued->license)));
// hand `$blob` to the customer as a .lic file

HTTP API

Mounted under the configured prefix (default licensing, throttled 60,1):

Method Path Body Response
POST /licensing/validate { key, fingerprint? } { valid, reason, claims }
POST /licensing/activate { key, fingerprint, label? } { valid, claims } / 422 / 404
POST /licensing/deactivate { key, fingerprint } { deactivated }
GET /licensing/composer/authorize/{pkg} HTTP Basic (email:key) 204 / 401 / 403

Client SDK (embed in the package you sell)

use Kurt\Modules\Licensing\Client\LicenseManager;

// auto-resolved from config('licensing.client.*'): server_url + key
$state = app(LicenseManager::class)->check();

$state->ok();        // true when valid OR within the offline grace window
$state->isGrace();   // true when the server was unreachable but a recent
                     // success is cached and still inside grace_days
$state->status;      // 'valid' | 'grace' | 'invalid'
$state->claims;      // product, policy, seats, expiry, licensee, ...

Offline verification of a signed file — no server, only the public key:

use Kurt\Modules\Licensing\Client\OfflineVerifier;

$result = (new OfflineVerifier($publicKey))->verifyBlob($blob);
$result->valid;   // signature + expiry checked locally

The machine fingerprint is stable and opaque (no raw host data leaves the machine):

use Kurt\Modules\Licensing\Client\Fingerprint;

Fingerprint::generate($installSalt);

Composer download gating

Apply the licensing.composer middleware to your private repository routes, or point an nginx auth_request at the bundled endpoint:

location ~ ^/dist/(?<pkg>.+)\.zip$ {
    auth_request /auth;
}
location = /auth {
    internal;
    proxy_pass https://licenses.example.com/licensing/composer/authorize/$pkg;
    proxy_pass_request_body off;
    proxy_set_header Authorization $http_authorization;
}

Composer sends the buyer's email as the username and the license key as the password; the bridge authorizes against the license's product, status, and (for updates_window) the release date.

Filament admin (optional)

If you run a Filament panel, register the version-dispatching plugin — the same call works on Filament 3, 4, and 5:

use Kurt\Modules\Licensing\Filament\LicensingPlugin;

$panel->plugin(LicensingPlugin::make());

It adds a Products resource (full CRUD) and a Licenses resource (browse, edit status/policy/seats, and a one-click revoke action). Licenses are minted through the issuer/API, so the Licenses resource has no "create" form by design.

Configuration

Key .env values:

Variable Purpose
LICENSING_SIGNING_KEY Ed25519 secret — signs license files (server only)
LICENSING_PUBLIC_KEY Ed25519 public — verifies files (server + clients)
LICENSING_KEY_HASH_SECRET HMAC secret for key hashing (defaults to app key)
LICENSING_SERVER_URL Client: base URL of the licensing API
LICENSING_KEY Client: this install's license key

See config/licensing.php for the full reference.

Testing

composer test    # Pest
composer stan    # PHPStan level 8
composer lint    # Pint (dry-run)

License

MIT © Ozan Kurt