Extending & Customizing
Extending & Customizing
Because every capability is a contract bound in the container, extension is just a binding.
Swap an implementation
Rebind any contract in a service provider — yours wins over the package default:
use Cbox\Id\Identity\Contracts\Subjects;
$this->app->singleton(Subjects::class, MySubjects::class);
Your class implements the same interface. Callers are untouched because they depend on the contract, not the class.
Decorate behaviour
Wrap the package implementation to add cross-cutting behaviour (metrics, extra validation, notifications) without forking it:
$this->app->extend(Subjects::class, function (Subjects $inner, $app) {
return new NotifyingSubjects($inner, $app->make(Notifier::class));
});
Override a model's tenant column
Tenant-owned models use the BelongsToTenant trait and default to an organization_id column.
Override tenantColumn() if your table differs. Every tenant-owned model must also implement
TenantOwned — that's what engages the global scope.
use Cbox\Id\Kernel\Tenancy\Concerns\BelongsToTenant;
use Cbox\Id\Kernel\Tenancy\Contracts\TenantOwned;
final class Widget extends Model implements TenantOwned
{
use BelongsToTenant;
public function tenantColumn(): string
{
return 'org_id';
}
}
Implementing an AssertionValidator
Federation deliberately leaves the SAML/OIDC signature validation to you, behind one contract, so you can wrap a vetted, maintained library rather than hand-rolling XML-signature or JWT verification (that's how XSW/XXE/alg-confusion breaches happen).
use Cbox\Id\Federation\Contracts\AssertionValidator;
use Cbox\Id\Federation\Contracts\Connections;
use Cbox\Id\Federation\Models\Connection;
use Cbox\Id\Identity\ValueObjects\FederatedPrincipal;
final class SamlAssertionValidator implements AssertionValidator
{
public function __construct(private readonly Connections $connections) {}
public function validate(Connection $connection, string $rawResponse): FederatedPrincipal
{
$config = $this->connections->config($connection); // decrypted IdP config
// Delegate to a vetted library (e.g. onelogin/php-saml), configured from $config,
// which verifies the signature, audience, conditions and timestamps and parses XML
// with external entities disabled. It MUST throw on anything it can't fully trust.
$assertion = /* ...validate with the library... */;
return new FederatedPrincipal(
provider: 'saml',
subject: $assertion->nameId(),
email: $assertion->attribute('email'),
name: $assertion->attribute('displayName'),
connectionId: $connection->id,
raw: $assertion->all(),
);
}
}
Bind it, then feed the trusted principal to FederationFlow::completeLogin() — the rest of the
login (user, membership, session, events, audit) is provider-agnostic and already built.
$this->app->singleton(AssertionValidator::class, SamlAssertionValidator::class);
Custom crypto / storage backends
The kernel contracts are pluggable too:
SecretBox— swap libsodium for a KMS/HSM-backed sealer for managed deployments.KeyManager— back signing keys with an external key store.RelationshipStore— replace the Postgres ReBAC engine if you ever outgrow it.
All keep the same contract, so the modules above them don't change.