Here is the part of this API that isn’t in any tutorial. Different consumers act for different Statamic accounts. When the provisioning service for one partner creates a license, it must be created with that partner’s Statamic credentials and nobody else’s.
So a consumer carries settings:
// app/Models/Consumer.php
protected function casts(): array
{
return ['settings' => 'encrypted:array'];
}
public function providerToken(): string
{
return $this->settings['statamic_token']
?? throw new MissingProviderCredentials($this->id);
}
encrypted:array means the column holds ciphertext. A partner’s provider token is as sensitive as your own, and it is never readable in a database dump.
providerToken() is the only code in the application that reads that key, and look at what it does when the key is missing. It throws. It doesn’t fall back to a shared token from config.
That is the most important line in this chapter. A fallback looks harmless and helpful: a consumer without its own credentials just uses the house account. It also means that every consumer whose settings are empty, misspelled, or reshaped by a migration lands silently in the same account, where each can list and delete the others’ licenses. An API that isolates its tenants by credentials has to fail closed when the credentials are absent. There is no default account. The shared token in config/services.php from Chapter 3 is deleted.
The driver learns to be built for a consumer:
// app/Services/License/StatamicDriver.php
public static function for(Consumer $consumer): self
{
return new self($consumer->providerToken());
}
And the binding from Chapter 3 changes in two ways:
// app/Providers/AppServiceProvider.php, in register()
$this->app->scoped(LicenseContract::class, function () {
$consumer = Auth::user()
?? throw new LogicException('No consumer.');
return StatamicDriver::for($consumer);
});
No controller changed. No request, no resource. Licenses::all() now reaches the provider as whoever is calling.
This is also why licenses need no Policy. A consumer’s requests are made with its own provider credentials, so another account’s licenses aren’t reachable at any URL. The isolation is exactly as strong as the credentials are distinct: two consumers given the same provider token share an account, and that should never happen by accident.
The Trap in the Binding
The second change is the word scoped, and it is worth a paragraph.
In Chapter 3 the driver was a singleton. A singleton lives as long as the process. On a queue worker, or under Octane, one process serves many consumers, and the driver built for the first would be reused for the second, with the first one’s credentials.
A scoped binding is a singleton for one request or one job. Laravel throws it away in between. Whenever something in the container depends on who is asking, it must not outlive the question. (Laravel documentation: Service Container › Binding Scoped Singletons.)
A queued job has no authenticated user at all. Chapter 10 shows how the job carries its consumer with it.