backend, Locastic Sep 15

Dynamically change access_control rules in Symfony

2 min read –

Voters are Symfony’s most powerful way of managing permissions. They allow you to centralize all permission logic, then reuse them in many places.

Symfony docs

What Symfony won’t tell you is that there is another very handy way of dynamically altering the “access_control” list. That list, defined in security.yaml is most often the starting point of every application and usually only after things get complicated, people decide to implement something more powerful.

In Locastic’s case, we have an application that was developed and improved through the years, and for all that time, it didn’t allow any anonymous access.

In this case, we are discussing a Sylius app. The application has multiple firewalls, multiple authenticators and authentication methods, a few voters and is even hosted on multiple domains (different frontend themes for every domain, that correspond to different channels).

So when our client demanded that they want to be able to define if some part of the app is private or public, we got an interesting task. After examining how Symfony reads access_control list, we found out that the simplest way to satisfy all the needs would be just to decorate the AccessMap service and the (slightly simplified) solution is:

class PublicStoreAccessMap implements AccessMapInterface
{
    public function __construct(
        AccessMapInterface $decorated,
        ChannelContextInterface $channelContext
    ) {
        $this->decorated = $decorated;
        $this->channelContext = $channelContext;
    }

    public function getPatterns(Request $request): array
    {
        $publicResponse = [
            ['IS_AUTHENTICATED_ANONYMOUSLY'],
            null,
        ];

        $route = $request->attributes->get('_route');

        /** @var Channel $channel */
        $channel = $this->channelContext->getChannel();
        if ($channel->isPublicStore()) {
            return $publicResponse;
        }

        return $this->decorated->getPatterns($request);
    }
}
# config/services.yaml
App\Security\PublicStoreAccessMap:
    decorates: 'security.access_map'

The Var $publicResponse has the format that Symonfy expects. You can debug the existing service to get more insight.

With this solution, we allowed users to have exactly what they wanted with having zero fear that we messed up something in refactoring such an important and complicated part of the app.

I’m aware that all of this can be achieved with the Voters and they are probably the best way of managing complex access rules, but this situation might be better when you have to define, tweak or change access_control in the runtime of an existing app.