Symfony2: Introduction to the Security Component part III

Posted on by Matthias Noback

Please note: I have revised this article to become part of the official documentation of the Security Component.

Authorization

When any of the authentication providers has verified the still unauthenticated token, an authenticated token will be returned. The authentication listener should set this token directly in the SecurityContext using its setToken() method.

From then on, the user is authenticated, i.e. means identified. Now, other parts of the application can use the token to decide whether or not the user may request a certain URI, or modify a certain object. This decision will be made by an instance of AccessDecisionManagerInterface.

An authorization decision will always be based on a few things:

    The current token

        The token`s getRoles() method will be used to retrieve the roles of the current user (e.g. "ROLE_SUPER_ADMIN")

    A set of attributes

        Each attribute stands for a certain right the user should have, e.g. "ROLE_ADMIN" to make sure the user is an administrator.

    An object (optional)

        Any object on which to decide, e.g. the current [Request](https://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html) object.

Access decision manager

Since choosing whether or not a user is authorized to perform a certain action can be a complicated process, the standard AccessDecisionManager itself depends on multiple voters, and makes a final verdict based on all the votes (either positive, negative or neutral) it has received. It recognizes several strategies:

        affirmative (default)

            Grant access as soon as any voter returns an affirmative response

        consensus

            Grant access if there are more voters granting access then there are denying

        unanimous

            Only grant access if none of the voters has denied access
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;

// instances of Symfony\Component\Security\Core\Authorization\Voter\VoterInterface
$voters = array(...);

// one of "affirmative", "consensus", "unanimous"
$strategy = ...;

// whether or not to grant access when all voters abstain
$allowIfAllAbstainDecisions = ...;

// whether or not to grant access when there is no majority (applies only to the "consensus" strategy)
$allowIfEqualGrantedDeniedDecisions = ...;

$accessDecisionManager = new AccessDecisionManager($voters, $strategy,
    $allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions);

Voters

Voters are instances of VoterInterface, which means they have to implement a few methods which allows the decision manager to use them:

        supportsAttribute($attribute)

            Will be used to check if the voter knows how to handle the given attribute.

        supportsClass($class)

            Will be used to check if the voter is able to grant or deny access for an object of the given class.

        vote(TokenInterface $token, $object, array $attributes)

            This method will do the actual voting and return a value equal to one of the class constants of [VoterInterface](https://api.symfony.com/master/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.html), i.e. VoterInterface::ACCESS_GRANTED, VoterInterface::ACCESS_DENIED or VoterInterface::ACCESS_ABSTAIN.

The security component contains some standard voters which cover many use cases:

The AuthenticatedVoter voter supports the attributes "IS_AUTHENTICATED_FULLY", "IS_AUTHENTICATED_REMEMBERED", and "IS_AUTHENTICATED_ANONYMOUSLY" and grants access based on the current level of authentication, i.e. is the user fully authenticated, or only based on a "remember-me" cookie, or even authenticated anonymously?

use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;

$anonymousClass = 'Symfony\Component\Security\Core\Authentication\Token\AnonymousToken';
$rememberMeClass = 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken';

$trustResolver = new AuthenticationTrustResolver($anonymousClass, $rememberMeClass);

$authenticatedVoter = new AuthenticatedVoter($trustResolver);

// instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface
$token = ...

// any object
$object = ...

$vote = $authenticatedVoter->vote($token, $object, array('IS_AUTHENTICATED_FULLY');

The RoleVoter supports attributes starting with "ROLE_" and grants access to the user when the required "ROLE_*" attributes can all be found in the array of roles returned by the token's getRoles() method.

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

$roleVoter = new RoleVoter('ROLE_');

$roleVoter->vote($token, $object, 'ROLE_ADMIN');

The RoleHierarchyVoter extends RoleVoter and provides some additional functionality: it knows how to handle a hierarchy of roles. For instance, a "ROLE_SUPER_ADMIN" role may have subroles "ROLE_ADMIN" and "ROLE_USER", so that when a certain object requires the user to have the "ROLE_ADMIN" role, it grants access to users who in fact have the "ROLE_ADMIN" role, but also to users having the "ROLE_SUPER_ADMIN" role.

use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
use Symfony\Component\Security\Core\Role\RoleHierarchy;

$hierarchy = array(
    'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_USER'),
);

$roleHierarchy = new RoleHierarchy($hierarchy);

$roleHierarchyVoter = new RoleHierarchyVoter($roleHierarchy);

Note

When you make your own voter, you may of course use its constructor to inject any dependencies it needs to come to a decision.

Roles

Roles are objects that give expression to a certain right the user has. The only requirement is that they implement RoleInterface, which means they should also have a getRole() method that returns a string representation of the role itself. The default Role simply returns its first constructor argument:

use Symfony\Component\Security\Core\Role\Role;

$role = new Role('ROLE_ADMIN');

// will echo 'ROLE_ADMIN'
echo $role->getRole();

Note

Most authentication tokens extend from AbstractToken, which means that the roles given to its constructor, will be automatically converted from strings to these simple Role objects.

Using the decision manager

The access listener

Normally, the access decision manager will already be asked to decide whether or not the current user is entitled to make the current request. This is done by the AccessListener, which is one of the firewall listeners that will be triggered for each request matching the firewall map.

It uses an access map (which should be an instance of AccessMapInterface) which contains request matchers and a corresponding set of attributes that are required for the current user to get access to the application.

use Symfony\Component\Security\Http\AccessMap;
use Symfony\Component\HttpFoundation\RequestMatcher;
use Symfony\Component\Security\Http\Firewall\AccessListener;

$accessMap = new AccessMap();
$requestMatcher = new RequestMatcher('^/admin');
$accessMap->add($requestMatcher, array('ROLE_ADMIN'));

$accessListener = new AccessListener($securityContext, $accessDecisionManager,
    $accessMap, $authenticationManager);

Security context

The access decision manager is also available to other parts of the application by means of the isGranted($attribute) method of the SecurityContext. A call to this method will directly delegate the question to the access decision manager.

use Symfony\Component\Security\SecurityContext;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

$securityContext = new SecurityContext();

if (!$securityContext->isGranted('ROLE_ADMIN')) {
    throw new AccessDeniedException();
}
PHP Security Symfony2 authorization documentation roles voters
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
Helen

Hi. I'll be very thankful if you have any suggestions how to implement user authentication and authorization keeping users into db with email and password credentials. There are many mentions in symfony specification but it difficult to put it all together.
Does 'login_check' action work automatically with database provider like it happens with in_memory one like described in specification? If not, how to setToken() to SecurityContext to let know what role and id current user has within application? Any suggestions how loginAction in controller looks like?

Matthias Noback

To get things working you should follow step by step what is shown in the Security documentation of The Book on www.symfony.com. All the extra information provided about the Security Component (including my blog posts on the subject) will likely not help you further in your pursuit for a first successful implementation of an application firewall.

gerda

hi, thanks for your reply. I already read the docs and the cookbook, just wanted to be sure it s good stuff before going for it. Will give it a try. Thanx

gerda

hi, it s very interesting but you dont talk about ACL at all in your security blogs? Is that because they not good enough? I m new to Symfony and the ACL on the docs looks really good but is it truewhen you actually implement them???

Matthias Noback

Hi Gerda,
That's right. I have skipped this part since everything is already well documented on the symfony.com site. But combine it with something like https://github.com/Problema..., since it can be very tedious to do it all manually.
Good luck with it,

coviex

Matthias, do you know if Security will work without Sf2?
This string from your first article on Security makes me doubt it:
$dispatcher->register(KernelEvents::REQUEST, array($firewall, 'onKernelRequest');
What to do if I use another framework (sf1.4) and don't have Kernel?

Matthias Noback

It could work, though it relies on quite a view core classes from Symfony2, like EventDispatcher, Request, Respone. Also configuring the component is quite a job, as you can see in both the Silex SecurityServiceProvider and the SecurityExtension class in the SecurityBundle.

cordoval

:D maybe a subbook on this or a video would be more plausible

Matthias Noback

The security component really has many facets, and is not very straight-forward. A walk-through while talking and showing code fragments works quite well as I know now from experience. But I would leave this to the Ryan and Leanna from KnpUniversity ;).