novvor / central-sdk-php
Server-side PHP SDK for Novvor Central licensing, tenant entitlements and one-time application launches.
Requires
- php: ^8.2
- firebase/php-jwt: ^7.0
- guzzlehttp/guzzle: ^7.0
- guzzlehttp/psr7: ^2.7
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
- squizlabs/php_codesniffer: ^3.13
README
Official server-side SDK for integrating PHP applications with the Novvor Central control plane.
It provides three deliberately small, tenant-bound capabilities:
- resolve the effective license and quotas of one application for one tenant;
- retrieve a signed, immutable v2 entitlement snapshot for synchronising modules, capabilities and limits;
- exchange a one-time Central launch code for an RS256-verified launch context.
The SDK does not create local users, roles, workspaces, or business records. Those decisions remain inside each consuming application.
Requirements
- PHP 8.2–8.5
- OpenSSL
- a Central application registration with the minimum required scopes
- HTTPS with a valid certificate
Installation
composer require novvor/central-sdk-php:^2.5
The package uses PSR-18 and PSR-17. You may inject your existing HTTP client and request factory or use the secure Guzzle factory.
use Novvor\CentralSdk\CentralClientFactory; use Novvor\CentralSdk\CentralConfiguration; $central = CentralClientFactory::create(new CentralConfiguration( baseUrl: config('services.novvor_central.url'), issuer: config('services.novvor_central.issuer'), appId: config('services.novvor_central.app_id'), appKey: config('services.novvor_central.app_key'), snapshotVerificationKey: config('services.novvor_central.entitlement_snapshot_key'), ));
Never commit the application or snapshot verification key. They are separate, product-scoped secrets and must come from a secret manager.
Installation contract
For repeatable onboarding, generate or construct the three public values
once from the registered application. The SDK deliberately never creates,
prints, or writes credentials into .env.
use Novvor\CentralSdk\CentralEnvironmentTemplate; $template = new CentralEnvironmentTemplate( baseUrl: 'https://central.example.com', issuer: 'https://central.example.com', appId: 'my_application', ); // Copy this output into an environment template or deployment configuration. // It contains public values only. echo $template->toDotenv();
Store ENIX_CENTRAL_API_KEY, ENIX_CENTRAL_WEBHOOK_SECRET, and
ENIX_CENTRAL_ENTITLEMENT_SNAPSHOT_VERIFICATION_KEY through the deployment
secret manager as three distinct, product-scoped values. They must never be
derived from each other, an application key, or an identity client secret.
CentralEnvironmentTemplate::deploymentContract() returns a machine-readable
checklist of these names and purposes without ever accepting or rendering their
values. Deployment automation can use it to prepare an application's contract
without turning the SDK into a credential distribution channel.
The package also exposes this as a Composer binary after installation:
vendor/bin/central-sdk-contract \ --base-url=https://central.example.com \ --issuer=https://central.example.com \ --app-id=my_application \ --format=json
It emits public configuration plus required secret names only. The Central
control plane must still register the application and grant least-privilege
scopes (for example entitlements.read and heartbeat.write) before the
application can connect.
Tenant entitlements
$entitlement = $central->tenantEntitlement( tenantPublicId: $tenant->public_id, correlationId: $request->header('X-Correlation-ID'), ); if (! $entitlement->isUsableAt()) { abort(403, 'This workspace is not licensed for the application.'); }
The SDK rejects responses whose application or tenant boundary differs from the request. Consumers should persist a short-lived projection only for resilience and must never turn a stale or missing projection into access.
Signed entitlement snapshots (contract v2)
Use the immutable snapshot only when an application needs to reconcile its local modules, capabilities or limits. The SDK requires a distinct product-scoped snapshot verification key and rejects missing contract headers, bad signatures, cross-boundary payloads and tampered snapshot hashes.
$snapshot = $central->tenantEntitlementSnapshot( tenantId: (string) $tenant->id, correlationId: $request->header('X-Correlation-ID'), ); $enabledModules = $snapshot->modules; $limits = $snapshot->limits;
Do not use a v2 snapshot to make an unauthenticated browser authorization decision. It is a server-to-server synchronisation projection. A snapshot that cannot be retrieved or verified must not grant new access.
One-time application launch
Central sends an opaque, single-use code to the application's /sso/consume
endpoint. The application exchanges it server-to-server:
$context = $central->exchangeLaunchCode( code: $request->string('code')->toString(), correlationId: $request->header('X-Correlation-ID'), );
The SDK validates:
- RS256 signature through Central JWKS;
- exact issuer;
- application audience;
- expiry and issuance time;
- unique assertion ID;
- required tenant and user boundaries.
Only after validation should the application map the Central identity to a local user and create its own session. Permission mapping must remain least-privilege and application-specific.
Authentication and request integrity
Authenticated requests include:
- the application credential;
X-Correlation-ID;- an ISO-8601 timestamp;
- a cryptographically random nonce;
- an HMAC-SHA256 signature over timestamp, nonce, and the exact request body.
Central enforces credential scope, application status, optional IP allowlists, timestamp tolerance, nonce replay protection, and audit logging.
Request signing is enabled by default. Disabling it is intended only for a controlled migration with a first-party registration that does not require HMAC.
Error handling
| Exception | Meaning |
|---|---|
CentralAuthenticationException |
Missing, invalid, or revoked credential |
CentralAuthorizationException |
Scope, application status, or policy denied |
CentralRateLimitException |
Central throttled the caller |
CentralUnavailableException |
Network or Central 5xx failure |
CentralProtocolException |
Invalid JSON, unsafe JWKS, bad JWT, or boundary mismatch |
Exceptions expose only HTTP status and correlation ID. Response bodies and credentials are intentionally not copied into exception messages.
Retry behavior
Only idempotent GET requests are retried, and only for transient network errors, 429, 502, 503, or 504. The one-time launch-code exchange is never retried by the SDK because replay semantics must remain explicit.
Operational guidance
- grant only
entitlements.readto entitlement consumers; - additionally grant
heartbeat.writeonly to applications reporting SDK integration evidence; - use a separate credential per app and environment;
- rotate credentials with overlap, then revoke the previous version;
- propagate correlation IDs through browser, app, jobs, and Central;
- treat Central unavailability as fail-closed for new sessions;
- monitor 401, 403, 429, JWKS failures, clock drift, and stale projections;
- never log application keys, launch codes, assertions, or complete claims.
See the integration contract and the release policy. See integration evidence for the authenticated runtime contract that Central uses to evaluate an external application's SDK readiness.
^2.5 is available through immutable release tags. Consumers must still use a
reviewed lockfile update and validate their own tenant-bound integration; an SDK
tag alone is not proof of a live Central connection.