Skip to content

Cookbook

Cookbook

Practical, copy-pasteable recipes. Each assumes you resolve contracts from the container.

Central login across your products

Your products don't embed the package — they log in against the running instance and reconcile identity. Server-side, provisioning a federated login is one call:

use Cbox\Id\Identity\Contracts\Subjects;
use Cbox\Id\Identity\ValueObjects\FederatedPrincipal;

$user = app(Subjects::class)->provisionFederated(
    new FederatedPrincipal('oidc', 'google|123', '[email protected]', 'Sam'),
);

provisionFederated is idempotent per (provider, subject): the first call creates the user and the identity link; later calls return the same user.

Set up a reseller → customer hierarchy

use Cbox\Id\Organization\Contracts\Organizations;
use Cbox\Id\Organization\Contracts\OrganizationHierarchy;
use Cbox\Id\Organization\ValueObjects\NewOrganization;
use Cbox\Id\Organization\Enums\OrganizationType;

$orgs = app(Organizations::class);
$reseller = $orgs->create(new NewOrganization('Contoso Partners', 'contoso', OrganizationType::Reseller));
$customer = $orgs->create(new NewOrganization('Northwind', 'northwind', parentId: $reseller->id));

$h = app(OrganizationHierarchy::class);
$h->manages($reseller->id, $customer->id);   // true — the reseller manages the customer
$h->descendants($reseller->id);              // ['<northwind id>']

A support role granted at the reseller now applies to the customer:

use Cbox\Id\AccessControl\Contracts\Roles;
use Cbox\Id\AccessControl\Contracts\AccessChecker;

$role = app(Roles::class)->define($reseller->id, 'support');
app(Roles::class)->grantPermission($role->id, 'tickets.manage');
app(Roles::class)->assign($reseller->id, 'support_1', $role->id);

app(AccessChecker::class)->can('support_1', 'tickets.manage', $customer->id); // true (rolls down)

Push capability gates from Stripe / Cashier

Wire your billing webhook to the entitlement writer. Billing translates the plan into capability gates (Cbox ID never sees the plan itself). Use reconcile() to guard against dropped webhooks — it upserts what's present and revokes what's absent:

use Cbox\Id\Kernel\Authorization\Contracts\EntitlementWriter;
use Cbox\Id\Kernel\Authorization\Enums\EnforcementMode;
use Cbox\Id\Kernel\Authorization\Enums\EntitlementSource;
use Cbox\Id\Kernel\Authorization\ValueObjects\EntitlementInput;

app(EntitlementWriter::class)->reconcile($org->id, [
    new EntitlementInput('feature.sso', ['enabled' => true]),                   // gate, from the plan
    new EntitlementInput('feature.export', ['enabled' => true]),
    new EntitlementInput('seats', ['limit' => 50], EnforcementMode::DecisionApi), // limit, checked live
], EntitlementSource::Billing);

Pick EnforcementMode::Claims for coarse, slow-changing gates (embedded in tokens), and EnforcementMode::DecisionApi (the default) for anything that must revoke immediately.

Keeping your own billing engine and want the full flow — reconcile, enforcement modes, provenance and events? See Entitlements & billing.

Provision users over SCIM

use Cbox\Id\Directory\Contracts\Directories;
use Cbox\Id\Directory\Contracts\DirectorySync;
use Cbox\Id\Directory\ValueObjects\ScimUser;

$registered = app(Directories::class)->register($org->id, 'Okta'); // token shown ONCE
$directory = $registered->directory;

app(DirectorySync::class)->provisionUser($directory->id, new ScimUser('okta|1', 'dana', '[email protected]'));

// Deprovision drops membership AND kills the user's sessions immediately:
app(DirectorySync::class)->deprovisionUser($directory->id, 'okta|1');

Register a webhook

use Cbox\Id\Webhooks\Contracts\WebhookRegistry;

$registered = app(WebhookRegistry::class)->register($org->id, 'https://app.acme.test/hooks', [
    'organization.created', 'user.login', 'entitlement.updated',
]);
// $registered->secret is the HMAC signing secret — store it once; verify X-Cbox-Signature.

Delivered domain events fan out automatically; failures retry with exponential backoff.

Verify the audit chain

use Cbox\Id\Kernel\Audit\Contracts\AuditLog;

$result = app(AuditLog::class)->verifyChain($org->id);
$result->valid;            // false if any entry was tampered, reordered or deleted
$result->brokenAtSequence; // where it broke

// Sign a checkpoint to anchor externally:
$checkpoint = app(AuditLog::class)->checkpoint($org->id);