Custom User Provider Factories for the Symfony Security Bundle

Symfony has some great documentation on adding custom security authentication providers, but there is a similarly mature system for user providers.

While there is support for custom user providers already which are defined as services within an application, I was looking for a way to provide something similar to the way memory user providers work: a resusable bit of configuration, shipped as a bundle, that an application could tie into.

User Provider Factories

This reusable user provider configuration is accomplished by implementing UserProviderFactoryInterface.

<?php

use Symfony\Bundle\SecurityBundle\DependencyInjection\UserProvider\UserProviderFactoryInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Some\Bundle\CustomUserProvider;

class CustomUserProviderFactory implements UserProviderFactoryInterface
{
    public function create(ContainerBuilder $container, $id, $config) : void
    {
        $container->register($id, CustomUserProvider::class)
            ->addArgument($config);
    }

    public function getKey() : string
    {
        return 'somebundle_custom';
    }

    public function addConfiguration(NodeDefinition $builder) : void
    {
        $builder->canBeEnabled();
        // maybe add other config options here should you desire
    }
}

This user provider would support this configuration in your app:

security:
  providers:
    main:
      somebundle_custom: ~

Note the somebundle_custom returned from the getKey method as well as used in the configuration. This key is how Symfony figures out which user provider factory to use.

addConfiguration is invoked when the security bundle processes its configuration. It can be used to define extra options that may go under somebundle_custom in the configuration. In this example, we just allowed the node to be enabled.

create is invoked when the container itself is built and should be used to register a user provider as a service at the given $id. This can set up anything else in the container that the provider may require.

Registering the Factory

To register the new user provider factory implementation, override the build method in a bundle. Pull the security extension out of the container and register the custom user provider factory with it.

<?php

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Some\Bundle\DependencyInjection\Security\CustomUserProviderFactory;

class SomeBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        // this throws if `security` is not present
        $security = $container->getExtension('security');

        $security->addUserProviderFactory(new CustomUserProviderFactory());
    }
}

When Should This be Used?

In my case, I used this to provide an easier way for applications to use a custom user provider that didn’t rely on them using custom configuration for a service-based user provider.

I don’t think this bit of Symfony extensibility has any place in a normal app — just use a service-based user provider. But it does make more sense in a reusable bundle that wants to let users easily access a custom user provider implementation. This is exactly what Doctrine does, for instance.