symfony/symfony · error · InvalidConfigurationException

Using "ldap_users_only" on the "%s" firewall requires a user

Error message

Using "ldap_users_only" on the "%s" firewall requires a user provider that returns "%s" instances, but none of the providers it uses does.

What it means

Thrown when you enable the "ldap_users_only" option on an LDAP-authenticating firewall (e.g. form_login_ldap) but the configured user provider(s) do not return Symfony\Component\Ldap\Security\LdapUser instances. The flag tells CheckLdapCredentialsListener to authenticate only users sourced directly from LDAP, so at container-compile time the bundle calls providesLdapUsers() which recursively inspects the provider (and ChainUserProvider legs) for an LdapUserProvider.

Source

Thrown at src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LdapFactoryTrait.php:65

        // well, so move the decorated one aside and hand the id back, to let both variants be
        // configured on the same firewall
        $decoratedId = 'security.authenticator.'.$key.'.'.$firewallName.'.inner';
        $container->setDefinition($decoratedId, $container->getDefinition($authenticatorId));
        $container->removeDefinition($authenticatorId);

        if (isset($definitions[$authenticatorId])) {
            $container->setDefinition($authenticatorId, $definitions[$authenticatorId]);
        }

        $authenticatorId = $decoratedId;

        if ($config['ldap_users_only']) {
            if (!property_exists(CheckLdapCredentialsListener::class, 'ldapUsersOnly')) {
                throw new InvalidConfigurationException('Using "ldap_users_only" requires symfony/ldap 8.2 or higher, the installed version would ignore it.');
            }

            if (false === self::providesLdapUsers($container, $userProviderId)) {
                throw new InvalidConfigurationException(\sprintf('Using "ldap_users_only" on the "%s" firewall requires a user provider that returns "%s" instances, but none of the providers it uses does.', $firewallName, LdapUser::class));
            }
        }

        $container->setDefinition('security.listener.'.$key.'.'.$firewallName, new Definition(CheckLdapCredentialsListener::class))
            ->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$firewallName])
            ->addArgument(new Reference('security.ldap_locator'))
            ->addArgument($config['ldap_users_only'])
        ;

        $ldapAuthenticatorId = 'security.authenticator.'.$key.'.'.$firewallName;
        $definition = $container->setDefinition($ldapAuthenticatorId, new Definition(LdapAuthenticator::class))
            ->setArguments([
                new Reference($authenticatorId),
                $config['service'],
                $config['dn_string'],
                $config['search_dn'],
                $config['search_password'],
            ]);

View on GitHub (pinned to 698e28026c)

Solutions

  1. Set the firewall's provider key to an LDAP user provider configured under security.providers with type ldap.
  2. If using a chain provider, ensure at least one leg is an LdapUserProvider.
  3. If you also need to authenticate non-LDAP users, remove ldap_users_only: true.

Example fix

# before
security:
  firewalls:
    main:
      provider: users_in_memory
      form_login_ldap:
        service: Symfony\Component\Ldap\Ldap
        dn_string: 'ou=users,dc=example,dc=com'
        ldap_users_only: true
# after
security:
  providers:
    my_ldap:
      ldap:
        service: Symfony\Component\Ldap\Ldap
        base_dn: 'ou=users,dc=example,dc=com'
        search_dn: 'cn=admin,dc=example,dc=com'
        search_password: secret
  firewalls:
    main:
      provider: my_ldap
      form_login_ldap:
        service: Symfony\Component\Ldap\Ldap
        dn_string: 'ou=users,dc=example,dc=com'
        ldap_users_only: true
Defensive patterns

Strategy: validation

Validate before calling

// Before enabling ldap_users_only, verify the provider chain yields LdapUser
use Symfony\Component\Ldap\Security\LdapUserProvider;

$providerId = $firewall['provider'] ?? null;
$def = $container->findDefinition($providerId);
$class = $def?->getClass();
if ($class && !is_a($class, LdapUserProvider::class, true)) {
    throw new \LogicException("ldap_users_only requires a LdapUserProvider; {$class} won't work.");
}

Type guard

function isLdapUserProvider(string $class): bool
{
    return is_a($class, \Symfony\Component\Ldap\Security\LdapUserProvider::class, true);
}

Prevention

When it happens

Trigger: Configuring a firewall with an authenticator whose key ends in "-ldap" (form_login_ldap, json_login_ldap, http_basic_ldap) and setting ldap_users_only: true, while the firewall/provider resolves to a non-LDAP provider (memory, entity, or a custom class not extending LdapUserProvider).

Common situations: Copying LDAP example config but leaving the default memory/entity provider in place; using a ChainUserProvider where none of the legs is an LdapUserProvider; enabling ldap_users_only after an upgrade without switching the provider to type ldap.

Related errors


AI-assisted analysis of symfony/symfony@698e28026c (2026-08-06). Data as JSON: /api/errors/3dce5ba93091fa9a. Report an issue: GitHub.