Requesters and Actors
The requester is who asks. It is typed mixed, and that is the point: this component decides without a token, without a user, and without a firewall.
Anything can be a requester
$accessControlManager->decide(new AccessRequest($token, 'EDIT', $post));
$accessControlManager->decide(new AccessRequest($apiKey, 'EDIT', $post));
$accessControlManager->decide(new AccessRequest($serviceAccount, 'EDIT', $post));
$accessControlManager->decide(new AccessRequest(null, 'PUBLIC_ACCESS'));A voter that does not understand a requester abstains. Nothing raises, nothing denies by accident.
Roles without Symfony Security
RoleVoter reads roles from a Symfony TokenInterface when it gets one. Otherwise it reads them from any requester implementing this component's own contract:
use AccessControl\Voter\RBAC\UserWithRoleInterface;
final readonly class ServiceAccount implements UserWithRoleInterface
{
/**
* @return list<string>
*/
public function getRoles(): array
{
return ['ROLE_IMPORTER'];
}
}An object that merely carries a getRoles() method works too, without implementing anything. That contract is deliberately this component's own: the notion of role is meant to leave Security, so nothing here types against Security's interface.
Roles are expanded through the role hierarchy before being matched, so holding ROLE_ADMIN answers a question about ROLE_USER when the hierarchy says so.
Who is really asking: the actor
Two situations share one shape under opposite names:
an administrator impersonating a user, who granted them nothing,
an agent a user has authorised to act for them.
The word that covers both is the one RFC 8693 uses: the requester is who the access is asked for, the actor is who is really asking.
Actor::of($requester) hands back the actor, or null when nobody else is acting, which is the ordinary case. Actor::isActedFor($requester) answers the same question as a boolean, and is what makes IS_IMPERSONATOR decidable where there is no token at all.
It is a contract on the requester rather than a wrapper around it, deliberately: wrapping would hide the requester from every voter that reads its type.
Symfony's SwitchUserToken is understood without implementing anything: Actor::of() returns its original token. The reference is soft, as everywhere here, so the component stands alone when symfony/security-core is absent.
Telling the component who is asking
Entry points do not receive a requester as an argument; they ask a RequesterProviderInterface for it.
Two implementations ship:
access_control.requester_provider.static
Always the same requester, typically a service account in a console context. The default.
TokenStorageRequesterProvider
The token currently in Symfony's token storage. Wired for you when Security is installed.
Replace the access_control.requester_provider alias to point the whole application at your own:
This is what makes #[AccessPolicy], the URL rules, the Twig functions and the console guards work in an application that has no Security at all: they all ask the same provider.
Asking on behalf of somebody else
RequesterBoundChecker is the service that binds the current requester to a question, and is what is_granted() and the controller helpers go through.
To ask about a requester that is not the current one, build the request yourself and hand it to the manager:
Last updated
Was this helpful?
