Skip to main content
Laravel, shipping fast.
Chapter 18 ยท Shipping Templates to Live Sites

A Live Editing Surface Needs Its Own Narrow, Expiring Credential

Julian Beaujardin

You've already met the internal credential this fleet uses for one service authenticating to another: a Bearer record, issued per API consumer, carrying its own settings, cached with a TTL that respects its actual expiry. That's the right shape for a job in api-server calling api-license. It's the wrong shape for a customer's browser, sitting on a live generated site, calling back into the platform to save an edit.

The trust boundary is different in kind, not degree. An internal Bearer token authenticates a service you wrote, on infrastructure you control, calling another service you also wrote. A live editing session authenticates a browser tab, on a device you've never seen, that a customer opened by clicking a link. Reusing the internal credential for that would put the same secret that lets api-server talk to api-license into a query string on the public internet. The editable system doesn't do that. It issues a purpose-built token instead, one that proves exactly one thing, "this browser is allowed to edit this one site, right now," and nothing else.

// app/Livewire/Editable.php (baked in by webplo:install-editable)
public ?string $siteId = null;
public bool $token_is_valid = false;
public ?string $token = null;
public ?string $webhookUrl = null;
public bool $canIndex = false;

public function mount(): void
{
    $this->siteId = Config::string('services.site.id');
    if (request('token')) {
        $this->token = request()->string('token')->value();
        $this->token_is_valid = $this->isTokenValid();
    }
}

On mount, the component reads a token query parameter and immediately checks it against the platform before trusting it for anything. It doesn't decide validity locally, it asks the one service that owns the answer, and reads back a small JSON envelope:

$valid = (bool) $response->json('data.valid', false);

if ($valid) {
    $webhookUrl = $response->json('data.webhook_url');
    $this->webhookUrl = is_string($webhookUrl) ? $webhookUrl : null;
    $this->canIndex = (bool) $response->json('data.can_index', false);
}

return $valid;

Notice what the token carries and what it doesn't. No customer identity, no password, nothing reusable across sites, just enough scope to do one job: data.valid gates whether the editing UI unlocks, data.webhook_url is a signed callback for this session so a saved edit can notify the dashboard, and data.can_index is a single entitlement flag, not a general permissions object. The component never assumes yesterday's validity still holds, it checks on every load, and when the merchant is done editing it calls back to expire the token explicitly, so a leaked or bookmarked link stops working the moment the session ends instead of drifting on until some fixed expiry.

That generalizes past this one feature: when a generated artifact you don't fully control needs to call back into your platform, give it a credential scoped to exactly that call, short-lived, independently revocable, and validated on every use, not the same class your own services use to trust each other. The internal Bearer token answers "which of my services is this." This token answers a narrower, harder question: "is this specific, temporary, browser-held session still allowed to do this one thing, right now." Answer both with the same credential and you've quietly widened your internal trust boundary to include every browser tab a customer has ever opened.

Closing the Loop

Three tiers, one installer, a rollout that's honestly two steps, a cache that doesn't know your fix happened until you tell it, a credential shaped for the boundary it actually crosses. None of it is exotic. It's what "one well-built API" looks like once you multiply it by a fleet and hand a piece of it to a customer's browser.

  • Distribution
    • [X] Split a generated-artifact system into a skeleton, a versioned package, and a static-asset host, each on its own cadence
    • [X] Let the package install itself through its own Artisan command, guarded and idempotent, instead of hand-copied files
  • Rollout
    • [X] Treat "tagged" and "reached every live instance" as two separate, separately tracked facts
    • [X] Bump the version constraint and redeploy as an explicit step, not an assumed side effect of tagging
  • Caching
    • [X] Invalidate any persistent runtime cache explicitly whenever a fix touches config-loading, and test the read right after the write
  • Trust boundaries
    • [X] Give a live, in-artifact editing surface its own narrow, expiring, platform-validated credential, never the internal service-to-service one

A fleet is not one API running many times. It's one API's discipline, tested against every place that discipline could quietly stop applying. Ship the pattern once, and mean it everywhere it runs.