← Security

SECURITY FEATURE

Protected Route

Add one attribute to any route and the framework enforces access — 403 returned 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

Add one access attribute and one optional permission attribute and the framework enforces access — 401 for unauthenticated requests, 403 for unauthorized ones.

Route Guards

Access is enforced before the handler runs

The guard chain decides whether the request should proceed, fail with 401/403, or bypass checks entirely for public endpoints.

// Protected: only admin can access
#[RequiresPermission('users.manage')]
#[AsPayload(path: '/admin/users', methods: ['GET'])]
class UserListPayload { ... }

// Public: no auth required
#[PublicEndpoint]
#[AsPayload(path: '/demo/routing/basic', methods: ['GET'])]
class BasicRoutePayload { ... }
Scenario Result
Authenticated + correct permission 200 OK
Authenticated + missing permission 403 Forbidden
Not authenticated 401 Unauthorized
#[PublicEndpoint] 200 OK (no auth check)
#[AsProtectedPayload] #[RequiresPermission] #[RequiresCapability] guard chain 403 response

Verified against Semitexa Ultimate 2026.09.19.1020

Protected Route

Mark a payload with #[AsProtectedPayload] and the framework enforces access — 401 for unauthenticated requests, 403 for authenticated subjects without the required permission.

How it works

The guard chain runs before the handler for every protected payload. #[RequiresPermission] on the payload declares the required permission slug; #[RequiresCapability] declares a coarse-grained capability gate. The chain evaluates the resolved principal against the declared grants and either allows the request through, returns 401 for unauthenticated subjects, or returns 403 for authenticated subjects without the required grant. #[AsPublicPayload] is the explicit opt-out for anonymous routes.

Why this matters

Access control declared on the payload is reviewable and enforced uniformly. There is no handler code that checks permissions manually, no way to forget the check in one handler but not another, and no logic duplication when two handlers share the same protection rule. Because the access attribute and the permission/capability attributes co-exist on the same class, a code reviewer sees both the security stance (public / protected / service) and the fine-grained guards in one read.

© Linus Torvalds: "Talk is cheap. Show me the code."

Handler Application entry point
<?phpdeclare(strict_types=1);namespace App\Application\Handler\Auth;use App\Application\Payload\Admin\UserListPayload;use App\Application\Resource\Admin\UserListResource;use Semitexa\Authorization\Attribute\RequiresPermission;use Semitexa\Core\Attribute\AsPayloadHandler;use Semitexa\Core\Contract\TypedHandlerInterface;#[RequiresPermission('users.manage')]#[AsPayloadHandler(payload: UserListPayload::class, resource: UserListResource::class)]final class ProtectedRouteHandler implements TypedHandlerInterface{    public function handle(UserListPayload $payload, UserListResource $resource): UserListResource    {        return $resource->fromUsers([]);    }}

Support Semitexa
Built for developers who prefer control over magic. Your support helps keep it fast, open, and evolving.

Donate via PayPal