Creating your own security attribute with Symfony

Nacho Colomina
2 min readDec 30, 2022

--

Symfony security has a great feature to check authorization named Voters. Voters are services which allows you to check user authorization in the way you need.

You can see more about Voters here: https://symfony.com/doc/current/security/voters.html

In this article, I would like to share with you another way to define custom authorization checking: Create our custom security attribute

Let’s imagine we store user roles out of our User class (the one which implements Symfony UserInterface) and we have a controller on which we only want to allow ROLE_SUPERUSER users.

Creating the attribute

Let’s create a simple attribute which receive the roles we want to allow

#[\Attribute]
class IsUserGranted
{
public function __construct(public readonly array $roles){ }
}

Creating the subscriber

Now, let’s create a subscriber which will keep listening to KernelEvents::CONTROLLER_ARGUMENTS.

class UserGrantedSubscriber implements EventSubscriberInterface
{

public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments']
];
}

public function onKernelControllerArguments(ControllerArgumentsEvent $event): void
{
$attrs = $event->getAttributes();

/** @var IsUserGranted[]|null $isUserGrantedAttrs */
$isUserGrantedAttrs = $attrs[IsUserGranted::class] ?? null;

if(!$isUserGrantedAttrs){
return;
}

$isUserGranted = $isUserGrantedAttrs[0];
// Some code to get user roles from any store
$roles = ['......'];

// ------------------------------------

$commonRoles = array_intersect(
$roles,
$isUserGranted->roles
);

if(count($commonRoles) === 0){
throw new AccessDeniedException('You are not granted to get this resource');
}
}
}

When KernelEvents::CONTROLLER_ARGUMENTS event is received, function onKernelControllerArguments will be executed. The function gets an object of class ControllerArgumentsEvent as a parameter. This object gives us access to controller attributes through getAttributes method. If IsUserGranted attribute is present, user roles will be compared against granted roles and, if there are no matching roles, an AccessDeniedException will be thrown.

Protecting a controller

The last step is to use our new attribute in a controller. Let’s create a controller and set it our attribute:

#[IsUserGranted(roles: ["ROLE_SUPERUSER"])]
class AdminController extends AbstractController
{
#[Route('/admin/domains', name: 'get_admin_domains', methods: ['GET'])]
public function getAdminDomains(): JsonResponse
{
return new JsonResponse([
'ares.com',
'atila.com',
], Response::HTTP_OK);
}
}

And that’s all. If we try to access the resource with a user who does not have a ROLE_SUPERUSER, access will be denied.

--

--