SECURITY FEATURE
Requires Permission
Access control should be declarative: the payload names the required permission, and the framework enforces it automatically.
Feature Guide
A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.
What this does
A payload can declare one required permission slug, and the framework enforces it before the handler is even called.
How it works
The authorization listener reads #[RequiresPermission] from the resolved payload, asks the authorizer for an access decision, and maps the result to 401 for guests or 403 for authenticated users missing the permission.
Why it matters
This keeps access control reviewable and removes defensive authorization code from handlers. The contract lives on the route boundary where reviewers expect to find it.
Key concepts
- #[RequiresPermission]
- Declares the exact permission slug required to execute the payload.
- 401 Unauthorized
- Returned when a guest subject hits a permission-protected route.
- 403 Forbidden
- Returned when the subject is authenticated but missing the declared permission.
- guard chain
- The authorization flow that evaluates the payload policy before the handler runs.
Permission Guard
One attribute, three outcomes
The same payload declaration produces different outcomes depending on whether the subject is a guest, authenticated without the permission, or authenticated with the permission.
#[RequiresPermission('users.manage')]
#[AsProtectedPayload(path: '/admin/users', methods: ['GET'])]
class ManageUsersPayload {}
final class ManageUsersHandler implements TypedHandlerInterface
{
public function handle(ManageUsersPayload $payload, AdminPageResource $resource): AdminPageResource
{
// No manual access checks here.
return $resource;
}
}
| Request state | Framework decision |
|---|---|
| Guest subject | 401 Unauthorized |
| Authenticated, missing permission | 403 Forbidden |
| Authenticated, permission granted | 200 OK |
Verified against Semitexa Ultimate 2026.09.19.1020
Requires Permission
Declare one permission slug on the payload and let the framework enforce it before your handler runs.
How it works
Place #[RequiresPermission('slug')] on any payload class. The guard chain intercepts every request to that route, resolves the current principal, and checks whether the permission is granted. Guests receive 401, authenticated subjects without the grant receive 403, and subjects with the grant reach the handler normally.
In templates: can()
A page shows only what the visitor may use. can('slug') asks the same Authorizer the route
guard asks, for the current request. can(null) means "signed in", and signed_in() asks
that directly:
{% if can('content.edit') %}<a ui="button" href="/admin/articles/{{ id }}/edit">Edit</a>{% endif %}
Hiding a control is never the check. The route, form action or grid action behind it refuses
on its own. can() keeps a page from offering what would be refused, so a handler never has to
compute and pass along canEdit-style flags. An action that runs outside a guarded route (a
form submit or a grid action through HUG) checks the same way with
UiPermissions::allows('slug').
Why this matters
Access control should be declarative. When the permission requirement lives on the payload, it is visible to reviewers alongside the route definition, enforced consistently by the framework without any handler code, and impossible to accidentally skip by forgetting a manual check.
How it works
The authorization listener reads #[RequiresPermission] from the resolved payload, asks the authorizer for an access decision, and maps the result to 401 for guests or 403 for authenticated users missing the permission.
Why it matters
This keeps access control reviewable and removes defensive authorization code from handlers. The contract lives on the route boundary where reviewers expect to find it.
Key concepts
- #[RequiresPermission]
- Declares the exact permission slug required to execute the payload.
- 401 Unauthorized
- Returned when a guest subject hits a permission-protected route.
- 403 Forbidden
- Returned when the subject is authenticated but missing the declared permission.
- guard chain
- The authorization flow that evaluates the payload policy before the handler runs.