How to Use Voters to Check User Permissions

In Symfony, you can check the permission to access data by using the
ACL module, which is a bit overwhelming
for many applications. A much easier solution is to work with custom voters,
which are like simple conditional statements.

Tip

Take a look at the
authorization
article for an even deeper understanding on voters.

In order to use voters, you have to understand how Symfony works with them.
All voters are called each time you use the isGranted() method on Symfony's
authorization checker (i.e. the security.authorization_checker service). Each
one decides if the current user should have access to some resource.

Ultimately, Symfony takes the responses from all voters and makes the final
decision (to allow or deny access to the resource) according to the strategy defined
in the application, which can be: affirmative, consensus or unanimous.

In this example, the voter will check if the user has access to a specific
object according to your custom conditions (e.g. they must be the owner of
the object). If the condition fails, you'll return
VoterInterface::ACCESS_DENIED, otherwise you'll return
VoterInterface::ACCESS_GRANTED. In case the responsibility for this decision
does not belong to this voter, it will return VoterInterface::ACCESS_ABSTAIN.

// src/AppBundle/Security/Authorization/Voter/PostVoter.phpnamespaceAppBundle\Security\Authorization\Voter;useSymfony\Component\Security\Core\Authorization\Voter\AbstractVoter;useAppBundle\Entity\User;useSymfony\Component\Security\Core\User\UserInterface;classPostVoterextendsAbstractVoter{constVIEW='view';constEDIT='edit';protectedfunctiongetSupportedAttributes(){returnarray(self::VIEW,self::EDIT);}protectedfunctiongetSupportedClasses(){returnarray('AppBundle\Entity\Post');}protectedfunctionisGranted($attribute,$post,$user=null){// make sure there is a user object (i.e. that the user is logged in)if(!$userinstanceofUserInterface){returnfalse;}// double-check that the User object is the expected entity (this// only happens when you did not configure the security system properly)if(!$userinstanceofUser){thrownew\LogicException('The user is somehow not our User class!');}switch($attribute){caseself::VIEW:// the data object could have for example a method isPrivate()// which checks the Boolean attribute $privateif(!$post->isPrivate()){returntrue;}break;caseself::EDIT:// this assumes that the data object has a getOwner() method// to get the entity of the user who owns this data objectif($user->getId()===$post->getOwner()->getId()){returntrue;}break;}returnfalse;}}

That's it! The voter is done. The next step is to inject the voter into
the security layer.

It tells Symfony that your voter should be called whenever an object of one
of the given classes is passed to isGranted(). For example, if you return
array('AppBundle\Model\Product'), Symfony will call your voter when a
Product object is passed to isGranted().

It tells Symfony that your voter should be called whenever one of these
strings is passed as the first argument to isGranted(). For example, if
you return array('CREATE', 'READ'), then Symfony will call your voter
when one of these is passed to isGranted().

The registered voter will then always be asked as soon as the method isGranted()
from the authorization checker is called. When extending the base Controller
class, you can simply call the
denyAccessUnlessGranted()
method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// src/AppBundle/Controller/PostController.phpnamespaceAppBundle\Controller;useSymfony\Bundle\FrameworkBundle\Controller\Controller;useSymfony\Component\HttpFoundation\Response;classPostControllerextendsController{publicfunctionshowAction($id){// get a Post instance$post=...;// keep in mind that this will call all registered security voters$this->denyAccessUnlessGranted('view',$post,'Unauthorized access!');returnnewResponse('<h1>'.$post->getName().'</h1>');}}

New in version 2.6: The denyAccessUnlessGranted() method was introduced in Symfony 2.6.
Prior to Symfony 2.6, you had to call the isGranted() method of the
security.context service and throw the exception yourself.

Imagine you have multiple voters for one action for an object. For instance,
you have one voter that checks if the user is a member of the site and a second
one checking if the user is older than 18.

To handle these cases, the access decision manager uses an access decision
strategy. You can configure this to suite your needs. There are three
strategies available:

affirmative (default)

This grants access as soon as there is one voter granting access;

consensus

This grants access if there are more voters granting access than denying;

unanimous

This only grants access if there is no voter denying access. If all voters
abstained from voting, the decision is based on the allow_if_all_abstain
config option (which defaults to false).

In the above scenario, both voters should grant access in order to grant access
to the user to read the post. In this case, the default strategy is no longer
valid and unanimous should be used instead. You can set this in the
security configuration: