Skip to content

Security and deployment ​

Island updates use your application's authorization, middleware, and CSRF protection. Signed snapshots protect state integrity; they do not grant access or hide data.

Application middleware remains authoritative ​

The plugin's default POST route is /_interact/update, named interact:update. It does not rerun the controller action that initially rendered an island.

Apply CakePHP's body parser, CSRF, and appropriate authentication middleware to update requests. The demo uses a test-only session identity; production applications must use their own authentication integration.

For separate administrative/public scopes, disable the plugin's default routes and connect the update controller within your existing route scopes:

php
use Interact\InteractPlugin;

$this->addPlugin(InteractPlugin::class, ['routes' => false]);
php
use Cake\Routing\RouteBuilder;

$routes->scope('/admin', function (RouteBuilder $routes): void {
    // Apply your application's existing administrative middleware here.
    $routes->post('/_interact/update', [
        'plugin' => 'Interact',
        'controller' => 'Interact',
        'action' => 'update',
        'interactScope' => 'admin',
    ], 'admin:interact:update');
});

Configure that view's Interact helper with scope: 'admin' and the URL generated by CakePHP's routing API for the named administrative route. The initial scope and update route's interactScope must match. Updating a snapshot through another scope is rejected.

Override authorize(IslandContext $context) to check current resource access on every request. Treat action arguments as untrusted resource identifiers and authorize their use. Signing a record ID does not permanently authorize it.

Applications using CakePHP Authorization can enable the optional AuthorizationTrait for policy checks and scopes using their existing middleware service and identity.

Request identification ​

The browser sends Interact: true on island update requests, including deferred initialization, polling, file transfers, and streaming actions. The plugin registers a CakePHP request detector during bootstrap:

php
$request->is('interact'); // Exact Interact: true header match.

Use the detector for middleware integration or logging. The header is supplied by the caller and does not establish authorization, CSRF validity, or snapshot integrity. Update endpoints still accept requests without it. Route scope configuration continues to identify endpoints for exception handling.

Generated JSON responses, including validation and safe error responses, and SSE responses carry Interact: true. This identifies the response format; the browser still validates the content type and protocol payload. Protocol versions remain in the payload.

Ordinary HTML documents, enhanced navigation requests, and native downloads do not receive the marker from Interact. The plugin does not echo a request header onto unrelated responses or replace application-handled responses.

Custom endpoints ​

Use the shared Interact\Http\UpdateHandler service when an island endpoint should belong to your application controller. CakePHP injects it into the action; returning its Response avoids automatic template rendering.

php
use Cake\Http\Response;
use Interact\Http\UpdateHandler;

public function update(UpdateHandler $interact): Response
{
    return $interact->handle(
        $this->getRequest(),
        $this->getResponse(),
    );
}

Connect the action in the application's existing route scope:

php
$routes->post(
    '/islands/update',
    [
        'controller' => 'Islands',
        'action' => 'update',
        'interactScope' => 'default',
    ],
    'app:interact',
);

The interactScope route default marks the endpoint early enough for middleware exceptions to receive Interact's safe JSON errors. Set it in server route configuration. The handler processes ordinary updates and file transfers through the same lifecycle and preserves the supplied response's application headers and cookies. Expected failures become JSON; unexpected failures retain normal CakePHP logging.

Point the rendering controller's helper at the named route:

php
// Import Cake\Routing\Router in this controller.
$this->viewBuilder()->addHelper('Interact', [
    'className' => 'Interact.Interact',
    'endpoint' => Router::url(['_name' => 'app:interact']),
    'scope' => 'default',
]);

The helper and route must declare the same scope. Apply the application's normal middleware to the custom action. You can disable the plugin's default route when you only use application-owned endpoints; the service remains available.

Tested custom endpoint implementation
php
<?php
declare(strict_types=1);

namespace TestApp\Controller;

use Cake\Http\Response;
use Cake\Routing\Router;
use Interact\Http\UpdateHandler;

/**
 * Hosts the upload example and demonstrates an application-owned island endpoint.
 *
 * The shared handler processes both updates and transfers. Route defaults declare
 * the scope early enough for middleware exceptions to retain island JSON feedback.
 */
class UploadsController extends AppController
{
    /**
     * Select this controller's named endpoint once for all mounted examples.
     */
    public function initialize(): void
    {
        parent::initialize();
        $this->viewBuilder()->addHelper('Interact', [
            'className' => 'Interact.Interact',
            'endpoint' => Router::url(['_name' => 'demo:uploads:update']),
            'scope' => 'uploads-demo',
        ]);
    }

    /**
     * Render the public document-upload demonstration through a normal action template.
     */
    public function index(): void {}

    /**
     * Delegate JSON and multipart handling through CakePHP's action dependency injection.
     */
    public function update(UpdateHandler $interact): Response
    {
        return $interact->handle($this->getRequest(), $this->getResponse());
    }
}

Snapshots ​

Snapshots are signed, session-bound, scope-bound JSON with a 30-minute default lifetime. The default signing service uses CakePHP's security salt with domain separation. Configure the snapshot lifetime and signing key through application settings. Use a strong secret and keep it out of source control. Application container replacements remain available for custom signing behavior.

Snapshots are visible to the browser. More transported props and state mean larger requests; computed viewData() does not add its results to the snapshot. Keep large lists and entities on the server. Snapshots provide integrity rather than encryption or single-use guarantees. Replaying a valid snapshot is possible until expiry. An application requiring idempotent payments or other durable operations must implement that guarantee in its business layer.

Signed tokens, including subscription leases and parent-input tickets, are limited to 512 KiB after encoding and signing. Issuing a larger token throws LengthException before it can be delivered as usable state. The limit includes identity metadata and Base64 overhead; the separate 1 MiB request-envelope limit does not increase it.

A failed connection does not establish whether the server performed an action. Interact does not automatically retry actions. Disposing an island or aborting fetch does not cancel server execution.

Do not cache personalized island HTML across sessions. Update responses use Cache-Control: no-store.

Markup and CSP ​

Escape application text and attribute values with CakePHP's helpers/functions. Returned HTML comes from trusted application templates; the plugin is not an HTML sanitizer.

The standalone runtime uses Alpine's CSP build and requires no unsafe-eval. Load scripts according to the application's CSP and nonce policy. Do not load the standalone bundle alongside an existing Alpine instance; use npm integration instead.

Binding, nesting, and transport payload sizes/depths are bounded. Prototype-related state keys and arbitrary PHP object restoration are rejected. Expired snapshots require a fresh mount, not an automatic replay of an action.

For safe error responses and middleware integration, see failures and recovery.

Redirect destinations must be validated by the application when they originate from user input. HTTP(S) external redirects are supported. Download URLs are same-origin and their endpoints must independently authorize file access; an island response is not an access token. See redirects and downloads.

Enhanced navigation ​

Page visits use ordinary CakePHP GET routes and run their normal middleware and authorization. Compatible document metadata guides browser replacement; it is not an authorization capability. Every destination island mounts and authorizes, and retained islands still authorize every update.

Navigation keeps the original document's HTTP security policy. Nonce-only rotation in default-src, script-src, script-src-elem, style-src, and style-src-elem is compatible when the rest of the policy and nonce alias relationships are unchanged. Both enforced and report-only policies participate in the fingerprint. Interact verifies incoming nonce markers against the fetched response's actual CSP headers before mapping advertised values to the original document's corresponding nonces. Unknown nonces are not upgraded; pre-stamped active nonce values that are absent from the response's advertised sources are removed. Nonce values use native nonce-hiding attributes rather than JSON content or custom attributes.

Substantive policy changes, changed session identity, and incompatible deployments require native navigation. The runtime still rejects executable inline scripts and import maps. Mapping a nonce does not replace CSP, sanitize application HTML, authorize an island, or make arbitrary inline code safe. Keep Interact's document middleware outside security-header middleware and account for headers added by other plugins or reverse proxies. Unsupported or inconsistent nonce metadata requires native navigation. See navigation.