gepur-it/ldap-bundle
gepur-it/ldap-bundle is a PHP/Symfony bundle that integrates LDAP directory services with your application, offering configuration for connections, user authentication, and directory queries. It helps wire LDAP-backed login and user lookup into Symfony services with minimal setup.
Before anything else: this is a Symfony bundle, not a Laravel package. The name (ldap-bundle), the DependencyInjection conventions, and the description ("Authorisation bundle fot gepur apps") all point at Symfony's security/DI stack. In a Laravel app you would not install it directly — you'd either port the relevant classes or, more realistically, use ldaprecord-laravel / adldap2 instead. The rest of this assessment assumes you're evaluating it against a Symfony service (or deciding whether to lift logic out of it).
Minimal steps:
composer require gepur-it/ldap-bundle:^5.1
Then register the bundle in config/bundles.php (it was written pre-Flex-autoconfig era, so expect manual registration) and inspect the bundle's DependencyInjection/Configuration.php first — that file is the only reliable documentation. It defines the config tree: connection host/port, base DN, bind DN/password, user search filter, and (for an authorisation bundle) the attribute or group filter used to derive roles.
Look at, in order:
DependencyInjection/Configuration.php — the real API surface (config keys, defaults).Security/ or Service/ — the user provider / authenticator and the group→role mapping.Resources/config/services.xml (or .yaml) — service IDs, so you know what to override.First use case to validate end-to-end: bind to the directory, search for a user by username, authenticate them, then map their LDAP group membership to Symfony roles. Get that working against a throwaway LDAP container before touching your real directory:
docker run -d -p 389:389 -e LDAP_ORGANISATION=Acme \
-e LDAP_DOMAIN=acme.test -e LDAP_ADMIN_PASSWORD=admin \
osixia/openldap:1.5.0
If the bundle can't authenticate against a fresh OpenLDAP with a trivially simple config, stop there — the maintenance risk isn't worth it.
Treat LDAP as the credential authority, keep your local user table as the profile store. The pattern that survives contact with real directories is: authenticate against LDAP → look up or create the local User → persist the LDAP-derived roles on that user record → let Symfony's normal session auth take over. This gives you a fallback when the directory is unreachable and lets you attach application data (preferences, audit fields) LDAP knows nothing about.
Map groups, don't hardcode roles. Almost every LDAP authorisation bundle exposes something like a group_filter / role_mapping map. Configure it declaratively rather than in code:
# illustrative — check Configuration.php for the real keys
ldap:
connection:
host: '%env(LDAP_HOST)%'
port: 636
encryption: ldaps
base_dn: 'ou=people,dc=acme,dc=test'
bind_dn: '%env(LDAP_BIND_DN)%'
bind_password: '%env(LDAP_BIND_PASSWORD)%'
user_filter: '(uid={username})'
group_filter: '(member={user_dn})'
role_map:
'cn=app-admins,ou=groups,dc=acme,dc=test': ROLE_ADMIN
'cn=app-users,ou=groups,dc=acme,dc=test': ROLE_USER
Wire credentials through env vars, never the config file. Bind DN passwords in YAML are the classic leak — they end up in a repo, a Docker layer, and a stack trace all at once.
Always keep a break-glass local admin. Configure the bundle to allow a local user provider alongside the LDAP one, or exempt an emergency account. You will need it the first time the directory or VPN goes down and nobody can log in.
Abstract the directory behind an interface. Even if you use the bundle's services directly, wrap them:
interface DirectoryAuthorizerInterface
{
public function rolesFor(string $username): array;
}
One adapter delegates to the bundle; a second returns hardcoded roles for tests. Directory-dependent tests are slow and flaky, and you'll want to exercise the role-mapping logic without a live LDAP.
Cache group lookups. Role resolution is one or more LDAP searches per login. If the bundle doesn't cache, decorate its service and add a short-TTL cache keyed on the user DN. A 5-minute TTL is enough to make group changes feel immediate without hammering the directory.
php >=7.4. A single constraint bump is a weak signal: no bugfixes, no security notes, no Symfony 6/7 compatibility work, still one star and zero dependents. Budget for the possibility that you own this code. Pin ^5.1 and vendor-fork it if you adopt it.php >=7.4 is an open-ended constraint, and that cuts both ways. The good news: Composer will no longer block installation on PHP 8.x, so the package is at least installable on a modern runtime. The bad news: >=7.4 has no upper bound, so it will happily install on PHP 8.4 where the removed ldap_control_paged_result*() functions do not exist — you get a runtime Call to undefined function instead of a clean dependency conflict. A declared floor of 7.4 also means the codebase still can't use PHP 8 syntax, so don't read this as "modernised". Run the auth flow on your actual production PHP version before committing.symfony/framework-bundle or symfony/security-bundle. If the constraints are still ^4.4|^5.x, the security system change in 6.x (authenticators replaced guards, security.yaml provider syntax changed) means you may be looking at a rewrite rather than an install. Check composer.json before you plan anything.user_filter: '(uid={username})' with an unescaped username lets *)(uid=* through. Verify the bundle calls ldap_escape($username, '', LDAP_ESCAPE_FILTER). If it doesn't, wrap it yourself. This is the single most common bug in hand-rolled LDAP integrations, and a release that only touched composer.json is no evidence it was fixed.ldapsearch -H ldaps://host -D "$BIND_DN" -W -b "$BASE_DN" '(uid=someone)' outside the app is always step one.ldapsearch but not the app" reports. Check whether the bundle sets LDAP_OPT_X_TLS_REQUIRE_CERT and whether it honours your system CA store.ldap_control_paged_result usage — the v5.1.0 release did not change the PHP floor beyond 7.4, so the removed-in-8.1 functions may still be in play here.(member={user_dn}) filter misses inherited memberships. Decide early whether you need LDAP_MATCHING_RULE_IN_CHAIN (1.2.840.113556.1.4.1941) and whether the bundle can express it.services.yaml with the same ID will override them. That, plus a custom user provider implementing UserProviderInterface, is your escape hatch when the bundled behaviour doesn't fit.ldap logging channel if present, and set kernel.debug so failed binds surface the underlying LDAP error code rather than a generic BadCredentialsException.Bottom line for day-to-day use: v5.1.0 is a compatibility unlock, not a revival — it lets Composer install the bundle on PHP 8 but does nothing about filter escaping, Symfony 6/7 security changes, or the removed paged-result functions. The concepts are standard (bind, search, map groups to roles) and worth borrowing; the code is not something to depend on unexamined. Read the two or three classes that matter, verify filter escaping and runtime behaviour on your actual PHP version, and be prepared to vendor it in. If you're on Laravel, skip it entirely and use a maintained LDAP package — this one will not meet you halfway.
How can I help you explore Laravel packages today?