PHPackages                             cboxdk/license - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. cboxdk/license

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

cboxdk/license
==============

Framework-agnostic, offline license core: mint and verify compact Ed25519-signed license artifacts (EdDSA JWTs) with entitlements, limits, deployment/domain binding, grace windows and signed revocation lists. The shared leaf both a billing issuer and a self-hosted verifier depend on.

v0.1.1(1mo ago)0527↓73.8%2MITPHPPHP ^8.4CI passing

Since Jul 16Pushed 1mo agoCompare

[ Source](https://github.com/cboxdk/license)[ Packagist](https://packagist.org/packages/cboxdk/license)[ Docs](https://github.com/cboxdk/license)[ RSS](/packages/cboxdk-license/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (2)Dependencies (8)Versions (3)Used By (2)

Cbox License
============

[](#cbox-license)

Framework-agnostic, offline license core for PHP 8.4+. It mints and verifies compact, signed **license artifacts** — an issuer holds an Ed25519 private key and signs licenses; every consumer bundles the matching public key and verifies them **fully offline**, with no network call and no license server to phone home to.

It is the shared leaf that two very different sides depend on: a billing/issuer service that sells and signs licenses, and a self-hosted deployment that has to decide, on its own, whether a given feature is licensed. Both link this one package so they agree — byte for byte — on the artifact format and the verification rules.

```
composer require cboxdk/license
```

What it is
----------

[](#what-it-is)

- **A signed artifact, not a checked-out token.** A license is a compact [EdDSA (Ed25519)](https://www.rfc-editor.org/rfc/rfc8037) JWT. Verification is a local signature check against a bundled public key — it works on an air-gapped box.
- **Deny-by-default.** Anything that is not provably a valid, in-window, correctly-bound, non-revoked license is refused. The verifier is a *total function*: `verify()` never throws — every failure comes back as a typed status with an operator-visible reason.
- **Entitlements are opaque data.** The verifier never hardcodes which plan grants what; it treats entitlements as opaque strings. Tiers are data owned by the issuer, not logic baked into the verifier.
- **A pure leaf.** No framework dependency. A host application wires it into its container in ~15 lines and depends only on the `Contracts\` interfaces.

Honest crypto
-------------

[](#honest-crypto)

Signing and verification wrap the vetted [`firebase/php-jwt`](https://github.com/firebase/php-jwt)library over libsodium's Ed25519 — no bespoke signing. The algorithm is **pinned to `EdDSA` on both sides**: the verifier decodes with a single allowed algorithm,

```
JWT::decode($licenseKey, new Key($this->publicKeyBase64, 'EdDSA'));
```

which closes `alg:none` and algorithm-confusion downgrade attacks (a token re-signed under HS256 with the public key as the HMAC secret is rejected at decode). Keys are base64-encoded raw sodium keys.

This library is **tamper-evident, not tamper-proof**: a determined operator with control of their own runtime can always patch the check out. The goal is a cryptographically sound, offline-verifiable licensing signal for honest deployments — not DRM.

Quickstart
----------

[](#quickstart)

### 1. Mint an issuer keypair (once, operator side)

[](#1-mint-an-issuer-keypair-once-operator-side)

```
use Cbox\License\Support\Ed25519KeyPair;

$keys = Ed25519KeyPair::generate();
// $keys['privateKey'] stays on the issuer; $keys['publicKey'] ships with verifiers.
```

### 2. Issue a license (billing / issuer side)

[](#2-issue-a-license-billing--issuer-side)

```
use Cbox\License\Ed25519LicenseIssuer;
use Cbox\License\ValueObjects\{LicenseRequest, LicenseLimits};
use Cbox\License\Capabilities;

$issuer = new Ed25519LicenseIssuer($privateKeyBase64);

$licenseKey = $issuer->issue(new LicenseRequest(
    plan: 'enterprise',
    entitlements: [Capabilities::SSO, Capabilities::SAML, Capabilities::ANALYTICS],
    limits: new LicenseLimits(organizations: 10, seats: 500, environments: null),
    customerId: 'cus_ACME',
    deploymentId: 'dep_ACME_PRIMARY',
    licensedDomain: 'id.acme.example',   // or null to leave domain-unbound
    issuedAt: new DateTimeImmutable('now'),
    notBefore: new DateTimeImmutable('now'),
    expiresAt: new DateTimeImmutable('+1 year'),
));
```

### 3. Verify it (self-hosted / consumer side, offline)

[](#3-verify-it-self-hosted--consumer-side-offline)

```
use Cbox\License\Ed25519LicenseVerifier;
use Cbox\License\ValueObjects\VerificationContext;

$verifier = new Ed25519LicenseVerifier(
    publicKeyBase64: $publicKeyBase64,
    graceSeconds: 7 * 24 * 3600,   // keep serving for a week past expiry
    clockSkewSeconds: 60,
);

$result = $verifier->verify($licenseKey, new VerificationContext(
    deploymentId: 'dep_ACME_PRIMARY',
    domain: 'id.acme.example',
    now: new DateTimeImmutable('now'),
    revocations: $revocationList,   // optional; null = no revocation check
));

if ($result->isLicensed()) {
    $entitlements = $result->entitlements();   // list, empty unless licensed
    $limits = $result->limits();               // LicenseLimits|null
}
// Otherwise inspect $result->status (an enum) and $result->reason (operator text).
```

Verification decision order
---------------------------

[](#verification-decision-order)

`verify()` resolves a status in this fixed order, short-circuiting on the first failure:

1. **Signature** (Ed25519, algorithm pinned) → `SignatureInvalid` on failure.
2. **Claims parse** (deny-by-default) → `Malformed` on failure.
3. **Not-before** (`nbf`, with skew) → `NotYetValid`.
4. **Expiry** (`exp`, with skew, then grace) → within period, else `InGrace`(still licensed), else `Expired`.
5. **Deployment binding** → `BindingMismatch`.
6. **Domain binding** (only when the license pins a domain) → `BindingMismatch`.
7. **Revocation** (only when a list is supplied) → `Revoked`.

Any unexpected error is caught and returned as `Unlicensed` — `verify()` never throws.

Revocation
----------

[](#revocation)

Revocation lists are themselves signed (same key, same pinned algorithm) and verified offline. They are **fail-open**: a missing, malformed, or tampered list is treated as "no revocations known" rather than bricking a legitimately licensed deployment. See [`docs/core-concepts/revocation.md`](docs/core-concepts/revocation.md).

Where this sits
---------------

[](#where-this-sits)

This package is UI-free primitives. If you want a finished, deployable identity platform built on top of it rather than assembling the app layer yourself, that lives in a separate application package that composes this core — this library does not ship it.

Requirements
------------

[](#requirements)

PHP `^8.4` (tested on 8.4 and 8.5), `ext-sodium`, and `firebase/php-jwt ^7.1`. See [`docs/requirements.md`](docs/requirements.md).

Development
-----------

[](#development)

```
composer lint          # Pint (code style)
composer analyse       # PHPStan level max
composer test          # Pest
composer license-check # dependency licenses are permissive
composer sbom          # regenerate the CycloneDX SBOM
composer qa            # everything above + composer audit
```

License
-------

[](#license)

MIT — see [`LICENSE`](LICENSE).

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance91

Actively maintained with recent releases

Popularity18

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

45d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b9761a79e61f2d5b9d650510dfb3555da18daf38f027aa84012c937e397e39a7?d=identicon)[cboxdk](/maintainers/cboxdk)

---

Top Contributors

[![sylvesterdamgaard](https://avatars.githubusercontent.com/u/2431914?v=4)](https://github.com/sylvesterdamgaard "sylvesterdamgaard (7 commits)")

---

Tags

phpjwtlicensecryptosignatureEd25519EdDSAframework agnosticofflinerevocationlicensingentitlementslicense-key

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/cboxdk-license/health.svg)

```
[![Health](https://phpackages.com/badges/cboxdk-license/health.svg)](https://phpackages.com/packages/cboxdk-license)
```

###  Alternatives

[paragonie/sodium_compat

Pure PHP implementation of libsodium; uses the PHP extension if it exists

937153.0M201](/packages/paragonie-sodium-compat)[ellaisys/aws-cognito

Laravel authentication with AWS Cognito, supporting web, API, SSO, MFA, WebAuthn, passkeys, and passwordless authentication.

122284.0k1](/packages/ellaisys-aws-cognito)[kinde-oss/kinde-auth-php

Kinde PHP SDK for authentication

22100.1k3](/packages/kinde-oss-kinde-auth-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
