Authorization
AuthorizationTrait gives islands a typed $this->Authorization object for CakePHP Authorization policy checks and scopes. The existing authorize(IslandContext $context) hook still controls access on initial mounts and subsequent requests.
$this->Authorization->authorize($article, 'update');Prerequisites
Configure CakePHP Authorization in your application following its documentation. Interact uses the current service and identity supplied by that middleware on both hosting pages and island update requests.
This integration supports CakePHP Authorization 3.x on CakePHP 5.4+.
Enable policy helpers
Add the trait once to a shared application island base class:
<?php
declare(strict_types=1);
namespace App\Island;
use Interact\Authorization\AuthorizationTrait;
use Interact\Island;
/**
* Supplies the application's islands with the existing middleware policy service.
* File: src/Island/AppIsland.php
*/
abstract class AppIsland extends Island
{
use AuthorizationTrait;
}Individual islands can also use the trait directly. No constructor changes or initialize() override are needed. $this->Authorization is read-only and request-local; it is not a prop or transported state.
Protect a resource
This example assumes the application supplies an ArticlesTable, an Article entity, and an authenticated identity exposing its id through array access. The mount receives an application-supplied $articleId. Each request reloads the entity and checks its current policy before reading its title into a draft.
<?php
declare(strict_types=1);
namespace App\Island;
use App\Model\Entity\Article;
use Interact\Attribute\Prop;
use Interact\Attribute\State;
use Interact\IslandContext;
/**
* Authorizes an article on every request while keeping its editable draft separate.
* File: src/Island/ArticleEditorIsland.php
*/
class ArticleEditorIsland extends AppIsland
{
#[Prop]
public int $articleId;
#[State(writable: true)]
public string $title = '';
protected Article $article;
public function authorize(IslandContext $context): void
{
$this->article = $this->fetchTable('Articles')->get($this->articleId);
$this->Authorization->authorize($this->article, 'update');
}
public function mount(): void
{
$this->title = $this->article->title;
}
}<?php
declare(strict_types=1);
namespace App\Policy;
use App\Model\Entity\Article;
use Authorization\IdentityInterface;
/**
* Allows the current owner to edit an article, including after permission changes.
* File: src/Policy/ArticlePolicy.php
*/
class ArticlePolicy
{
public function canUpdate(?IdentityInterface $identity, Article $article): bool
{
return $identity !== null && $identity['id'] === $article->user_id;
}
}<?php
/** File: templates/island/article_editor.php */
?>
<?= $this->Form->control('title', ['x-interact:model' => 'title']) ?><?php
/** @var int $articleId Authorized application context selecting the article. */
?>
<?= $this->Interact->render('ArticleEditor', ['articleId' => $articleId]) ?>The entity stays in a protected PHP property. It is never serialized into a snapshot. mount() initializes the draft only once; later requests preserve edits while authorize() loads the current resource and rechecks the policy. Draft synchronization does not save the article.
Check an action's resource
An allowed island does not grant access to other records named in action arguments. Load and authorize those resources in the action before using them:
use Interact\Attribute\Action;
#[Action]
public function inspect(int $otherArticleId): void
{
$other = $this->fetchTable('Articles')->get($otherArticleId);
$this->Authorization->authorize($other, 'view');
// Application behavior using this authorized resource follows here.
}This assumes an application canView() policy in addition to the preceding example's canUpdate(). For streaming actions, perform the island's access check in authorize() before response emission; additional action-resource checks can still run inside the action.
Policy helpers
| Method | Behavior |
|---|---|
authorize($resource, $action) | Throws the native Authorization\Exception\ForbiddenException on denial, preserving its policy result. |
can($resource, $action) | Returns a boolean for an application decision or presentation flag. |
canResult($resource, $action) | Returns the native Authorization\Policy\ResultInterface. |
applyScope($resource, $action, ...$args) | Returns the policy scope's result, forwarding additional arguments. |
skipAuthorization() | Explicitly acknowledges the middleware's required-check tracking for the whole HTTP request. |
Always supply the policy action. authorize($article, 'update') checks canUpdate() even if the island action is named save, or the request is a mount, draft synchronization, upload, history restoration, or subscription renewal. The update controller's action name is not used to select a policy.
Use can() for PHP-rendered presentation data where useful. Hiding a button does not authorize its action: retain the server-side policy check.
Guest policies
When no identity is present, the middleware service receives null as the policy's identity argument. Policies decide whether guests can access a resource. A non-null identity that does not implement the Authorization plugin's IdentityInterface is a configuration error, not a guest.
Query scopes
Apply query scopes before executing or paginating a query:
$query = $this->fetchTable('Articles')->find('published');
$query = $this->Authorization->applyScope($query, 'index');
$articles = $this->paginate($query);This assumes an application findPublished() finder, an ArticlesTablePolicy with scopeIndex(), and the island's declared pagination state. Keep domain query logic in Table finders and access restrictions in the policy. Scoping a listing does not authorize an individual record mutation.
Explicit public requests
When middleware requires an authorization check, a public island can explicitly acknowledge that the request needs none:
public function authorize(IslandContext $context): void
{
$this->Authorization->skipAuthorization();
}skipAuthorization() marks the whole HTTP request as checked. It does not grant access to protected child islands or bypass their hooks. A successful check in one island also satisfies the middleware's request-level tracking; each protected island must still implement its own resource checks.
The trait never skips automatically. Existing islands without the trait retain their current behavior, including any application middleware requirements.
Custom request attributes
Override the factory in AppIsland if your application uses different attribute names or needs a bridge subclass:
use Interact\Authorization\IslandAuthorization;
protected function createAuthorization(): IslandAuthorization
{
return new IslandAuthorization(
$this->getRequest(),
identityAttribute: 'member',
serviceAttribute: 'policyService',
);
}The defaults are identity and authorization. Configure the application middleware consistently with these names; the trait does not rename attributes. No global Interact configuration is required.
Failures and testing
Denied updates retain Interact's safe 403 response and preserve the browser draft. Streaming failures after emission use terminal failure frames, and denied subscription renewals retain their existing member-error response. Application policy details are not copied into those browser errors.
Missing packages, missing middleware services, and invalid service or identity attributes produce configuration exceptions. They never silently allow access.
The island test helpers accept a request containing your real authorization service and decorated identity. Middleware tracking and authentication integration should also be tested through native CakePHP HTTP integration tests. See Security and Lifecycle.