Symfony2 Security: Creating dynamic roles (using RoleInterface)

Posted on by Matthias Noback

The Symfony Security Component provides a two-layer security system: first it authenticates a user, then is authorizes him for the current request. Authentication means "identify yourself". Authorization means: "let's see if you have the right to be here".

The deciding authority in this case will be the AccessDecisionManager. It has a number of voters (which you may create yourself too). Each voter will be asked if the authenticated user has the right "roles" for the current URL.

Configuring (static) roles

In security.yml you can add "access control" rules, to define the required roles:

security:
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }

In the same file, you can add a "role hierarchy", which defines a hierarchy of roles. The configuration below means: "a user who has the role "ROLE_ADMIN" may also see any page which requires "ROLE_USER":

security:
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

Each role appears to be just a string (not even a constant!). This gives you immense flexibility. "Defining a new role" is nothing more than dreaming up a new string, therefore, there is no cookbook article about it...

Yet, roles which are just strings, are also very inflexible, as in "very static". Let's say we want to have roles which are in some way dependent on a property of the authenticated user. We can accomplish this by creating role objects, which implement RoleInterface. In fact, every role defined as a string is converted "under the hood" to a role object.

Creating (dynamic) roles

The AccessDecisionManager collects the current set of roles by calling the getRoles() method on the authenticated user. This method is defined in UserInterface, which each user object should implement. So, take your custom user class and modify the getRoles():

namespace Matthias;

use Symfony\Component\Security\Core\User\UserInterface;
use Matthias\UserDependentRole;

class User implements UserInterface
{
    public function getRoles()
    {
        return array(
            'ROLE_USER', // normal "string" role
            // ...,
            new UserDependentRole($this), // dynamic role
        );
    }
}

The UserDependentRole is a class we should still define. It should implement RoleInterface. You can make it do anything, for example:

namespace Matthias;

use Symfony\Component\Security\Core\Role\RoleInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class UserDependentRole implements RoleInterface
{
    private $user;

    public function __construct(UserInterface $user)
    {
        $this->user = $user;
    }

    public function getRole()
    {
        return 'ROLE_' . strtoupper($this->user->getUsername());
    }
}

RoleInterface just requires you to implement the method getRole(), which should return a string that uniquely identifies the current role.

Now, in case the user "admin" is authenticated, he now also has a role called "ROLE_ADMIN". Also, a user "matthias" has a role "ROLE_MATTHIAS".

Using this UserDependentRole makes it possible to allow only users with a specific username to enter certain parts of your site. Though this is generally to be considered "bad design", this post gives you an idea as to what the possibilities are of using a custom role.

If you have any good use cases for this, I am happy to hear about them!

PHP Security Symfony2 authorization roles
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).
Dylan Byrne

Thanks man! I was wondering how to assign a role based on a users information in the database :)

Matthias Noback

You're welcome, thanks for letting me know!

Hpatoio

Hello. Is it possible to assign different role depending on the firewall the user use to login ?
I've an app with 2 firewalls that share the save user provider. One use a form to login the user and the second one an access_token. Everything works but I want user that when the user login via access token (a custom link in a mail or similar) has only a some ROLE and not all. Thanks

Maria

I have a question,

I've defined 3 roles in the security.yml file ROLE_1, ROLE_2 and ROLE_3
when I authenticate to the application I show this code:
Roles: {% for role in app.user.roles %}{{ role.nombre }} {% if not loop.last %}, {% endif %}{% endfor %}

and in the web appears: ROLE_1 ,ROLE_2 , ROLE_3
but when I use the method is_Granted('ROLE_1') it returns false, I don't know why

Antoni

Hi,
While developing new symfony based app we needed to make custom user classes. Ex:
class Employee extends User {}
class Administrator extends User {}

class User implements UserInterface {
...
}

it's done with Doctrine single-table inheritance mapping.

In fact, The User role depends on the class name and user can have only one role - so there is no need to have "roles" field in th DB.

Im wondering which approach is "better" and why:
- to use your idea (like classDependentRole with getRole() { ROLE_.classname($user) } (sry for "pseudo-code ;)"
- to define in Employee/Admin classes:
Function getRoles() { return arrray('ROLE_EMPLOYEE') } (for employee class)
-> "hardcoded"

And...
Why using special RoleClass instead of writing proper code in
User::getRoles() ?

Greetings!

Mfana Ronald Conco

Thanks for the post, I was curios about the role_hierarchy but never attempted it - I will give it a try...

Pierre

This, sir, is very clever. Thank you! I think I will use this technique in the near future :-)

cordoval

my use case is i have a feature that two types of User ROLEs are only allowed to see. How to approach this? Basically only one user will be admin (maybe ROLE_SPECIALFEATURE_ADMIN ?? ) and then the others will be ROLE_SPECIALFEATURE_USERS?? . How to accomplish this?

cordoval

nevermind i found it :)

Nice one. Was looking for something written on this. Use case I'm looking at is admin-defined roles stored in db