groupesti / laravel-license
Software licensing for Laravel: signed license tokens (JWT EdDSA/RS256), products/editions/features, device activations and validation logging.
Requires
- php: ^8.3
- ext-openssl: *
- ext-sodium: *
- illuminate/console: ^11.0 || ^12.0 || ^13.0
- illuminate/database: ^11.0 || ^12.0 || ^13.0
- illuminate/http: ^11.0 || ^12.0 || ^13.0
- illuminate/routing: ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^11.0 || ^12.0 || ^13.0
- illuminate/validation: ^11.0 || ^12.0 || ^13.0
- ramsey/uuid: ^4.7
Requires (Dev)
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- phpunit/phpunit: ^10.5 || ^11.0 || ^12.0
README
Software licensing for Laravel: issue signed license tokens (JWT, EdDSA or RS256), validate them, manage products / editions / features, track per-device activations and log every validation — fully configurable and self-contained.
Installation
composer require groupesti/laravel-license php artisan vendor:publish --tag=license-config php artisan migrate
Signing keys are read from configuration (never the database). Generate an Ed25519 key pair and set:
LICENSE_ACTIVE_KID=default LICENSE_ALGORITHM=EdDSA LICENSE_PRIVATE_KEY="base64-or-PEM-private-key" LICENSE_PUBLIC_KEY="base64-or-PEM-public-key" LICENSE_ISSUER="https://your-app.example"
Concepts
- Product / Edition / Feature — your catalogue. An edition bundles features (with optional per-feature limits).
- License — issued to a licensee (any model, via the
HasLicensestrait), carries atypeandstatus, validity window and device cap. - Activation — one row per device that has activated a license.
- ValidationLog — an audit trail of every validation attempt.
LicenseStatus, LicenseType, ActivationStatus and ValidationResult are
Eloquent models (reference tables) — you can add your own rows. The package
seeds the default values and exposes slug constants (e.g. LicenseStatus::ACTIVE).
Issuing a license
use License\Actions\IssueLicense; use License\Models\LicenseStatus; use License\Models\LicenseType; $license = app(IssueLicense::class)->execute([ 'product_id' => $product->id, 'edition_id' => $edition->id, 'license_type_id' => LicenseType::idFor(LicenseType::SUBSCRIPTION), 'license_status_id' => LicenseStatus::idFor(LicenseStatus::ACTIVE), 'name' => 'Acme Corp', 'max_devices' => 5, 'valid_from' => now(), 'valid_until' => now()->addYear(), ]); $token = $license->plain_token; // signed JWT, returned once on creation
Or from the CLI:
php artisan license:create --product=my-app --edition=pro --type=subscription --email=customer@example.com
Validating a license
use License\Actions\ValidateLicense; $result = app(ValidateLicense::class)->execute($token, $deviceId, [ 'ip_address' => request()->ip(), ]); // ['valid' => true, 'result' => 'valid', 'license' => [...], 'claims' => [...], 'expires_at' => '...']
Public HTTP endpoint (rate limited): POST /api/v1/license/validate.
Online (real-time) verification
Set online_verification on a license to require real-time validation. The
token then carries the flag and a heartbeat schedule (interval / grace,
from config('license.online_verification')), and the client must call the
validation endpoint at least once per interval.
- Licenses with
online_verificationare validated by the endpoint (live status, revocation and device checks); the response includes aheartbeatwith the next check-in time. - Licenses without it are meant to be validated offline — the endpoint
refuses them with
result: online_not_required(HTTP 409).
php artisan license:create --product=my-app --type=subscription --online
Attaching licenses to a model
use License\Traits\HasLicenses; class Team extends Model { use HasLicenses; // $team->licenses() }
Commands
php artisan license:create # create and sign a license php artisan license:validate # validate a token php artisan license:revoke # revoke by id or jti
HTTP API
POST /api/v1/license/validate— public validation./api/v1/admin/licenses/*— admin CRUD + catalogue (guarded byadmin_api_middleware).
Translations
Provided for en, fr, es.
php artisan vendor:publish --tag=license-lang
License
MIT. See license.md.