New in Symfony 2.8: Simpler Security Voters

Contributed by
Grégoire Pineau in #16601.

Security Voters are the recommended way to check for user permissions in Symfony applications. In Symfony 2.6 we introduced an AbstractVoter class to reduce the boilerplate code needed to define a voter. Instead of implementing the VoterInterface, you could just extend from AbstractVoter.

In Symfony 2.8 we have simplified voters a bit more thanks to the new Voter class. This article shows the main changes needed to upgrade a voter to use the new class. The first step is to make your voters extend the new Voter class instead of the previous AbstractVoter class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace AppBundle\Security\Voter;

use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

// Before Symfony 2.8
class CustomVoter extends AbstractVoter { ... }

// In Symfony 2.8
class CustomVoter extends Voter { ... }

The second important change is that supportsAttribute() and supportsClass() from VoterInterface are deprecated in Symfony 2.8 and they are removed in 3.0. These methods (and the getSupportedAttributes() and getSupportedClasses() methods of the AbstractVoter class) have been replaced by the new supports($attribute, $subject) method, which checks if the given attribute and "subject" are supported by the voter.

The "subject" can be anything you want to secure. Although it's usually an object (e.g. BlogPost, Invoice, Product) you can also pass simple variables like a string (e.g. the path to a resource to be downloaded).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Before Symfony 2.8
class CustomVoter extends AbstractVoter
{
    const VIEW = 'view';
    const EDIT = 'edit';

    protected function getSupportedAttributes()
    {
        return array(self::VIEW, self::EDIT);
    }

    protected function getSupportedClasses()
    {
        return array('AppBundle\Entity\BlogPost');
    }
}

// In Symfony 2.8
use AppBundle\Entity\BlogPost;

class CustomVoter extends Voter
{
    const VIEW = 'view';
    const EDIT = 'edit';

    public function supports($attribute, $subject)
    {
        return $subject instanceof BlogPost && in_array($attribute, array(
            self::VIEW, self::EDIT
        ));
    }
}

The last change is the introduction of a new voteOnAttribute() method which performs a single access check operation on a given attribute, "subject" and the security token (this is almost identical to AbstractVoter class):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// ...
use Symfony\Component\Security\Core\User\UserInterface;
use AppBundle\Entity\User;

// Before Symfony 2.8
class CustomVoter extends AbstractVoter
{
    // ...

    protected function isGranted($attribute, $post, $user = null)
    {
        if ($attribute === self::VIEW && !$post->isPrivate()) {
            return true;
        }

        if (!$user instanceof UserInterface) {
            return false;
        }

        if ($attribute === self::EDIT && $user->getId() === $post->getOwner()->getId()) {
            return true;
        }

        return false;
    }
}

// In Symfony 2.8
class CustomVoter extends Voter
{
    // ...

    protected function voteOnAttribute($attribute, $post, TokenInterface $token)
    {
        if ($attribute === self::VIEW && !$post->isPrivate()) {
            return true;
        }

        $user = $token->getUser();
        if (!$user instanceof User) {
            return false;
        }

        if ($attribute === self::EDIT && $user->getId() === $post->getOwner()->getId()) {
            return true;
        }

        return false;
    }
}

In summary, the new Voter class requires you to implement two methods:

1
2
3
4
5
6
7
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

abstract class Voter implements VoterInterface
{
    abstract protected function supports($attribute, $subject);
    abstract protected function voteOnAttribute($attribute, $subject, TokenInterface $token);
}

The existing AbstractVoter class has been deprecated since version 2.8 and it's been removed in 3.0. If you plan to upgrade to Symfony 3 soon, start updating your voters to use the Voter class instead.

Comments

AbstractVoter will not be removed in 3.0. It has been removed in 3.0. You should update your blog post template for deprecations as 3.0 is already released
@Christophe I've fixed the error. Thanks!
supportsAttribute should be supports in the second code example.
@Wouter very nice catch! Fixed. Thanks.
"have been replaced by the new supports($attribute, $subject) method" in the article, but "public function supportsAttribute($attribute, $subject)" in the code, which one is correct?
@Alex the old method is `supportsAttribute()` and the new one `supports()`. I've updated the sample code to fix the previous error. Thanks!
Is use AppBundle\Entity\User (still) necessary in this case ?
@Marc in this example is needed because we do this check:

if (!$user instanceof User) {
return false;
}
So if I understand this correctly in the newer example we could ommit
use Symfony\Component\Security\Core\User\UserInterface; ?
@Marc yes, but I added it because it's required by the old example.
I remember the old way to do the same thing in Sf < 2.6, the new version is REALLY simpler! Thanks a lot for this improvement!

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.