Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ldap User Provider Bundle Laravel Package

wiser/ldap-user-provider-bundle

A Symfony bundle that supplies an LDAP user provider so applications can authenticate against directory servers and load user records from LDAP. It reduces custom integration code by wiring user lookup into Symfony's security layer with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

wiser/ldap-user-provider-bundle is a Symfony 6.4 bundle that plugs LDAP (OpenLDAP, Active Directory, FreeIPA) into the Security component as a user provider. If you're coming from Laravel, think of it as the equivalent of a custom UserProvider/Eloquent user provider, except the "database" is a directory and the "query" is a bind/search pair. It does not replace the firewall — it supplies the users half of it.

Minimal path:

  1. Confirm the hard requirements. PHP 8.1+ with ext-ldap enabled (php -m | grep ldap), Symfony 6.4's symfony/security-bundle, and network reachability to the LDAP host. This is a 6.4-only package — don't expect it to slide into a 7.x upgrade untouched.
  2. Install and register. composer require wiser/ldap-user-provider-bundle. With Flex the bundle registration in config/bundles.php is usually automatic; if not, add it manually. There is no recipe to lean on for config scaffolding, so you will be hand-writing YAML.
  3. Look at the repo's README first, then the Resources/config/services.yaml (or *.php service config). With a package this small, the service definitions are the documentation. Note the service ID of the user provider and any parameters it consumes — you'll reference that ID in security.yaml.
  4. Configure the connection: host/port, ldap:// vs ldaps://, StartTLS toggle, the search base DN, the bind DN + password for the service account, the search filter (e.g. (sAMAccountName={username})), and an attribute map (username, email, display name, groups).
  5. Wire it up in config/packages/security.yaml under providers: and point your form-login firewall at that provider name.

First real use case: an internal app where HR/Sysadmin already owns user accounts in AD and you don't want a second credential store. You authenticate against AD, map memberOf to ROLE_*, and either keep users purely in-session or mirror them into a local table for relations.

Implementation Patterns

Two-phase bind (the one you actually want). Avoid bind-as-the-user (bind with the entered credentials, done). Instead: bind with a read-only service account → search the base DN for the entry matching the username → retrieve the DN → re-bind with that DN and the submitted password. This gives you a stable DN (logins survive a cn rename), lets you read attributes before authentication, and keeps search permissions narrow.

Mirror-on-first-login. refreshUser() runs on every request, and directory lookups are network I/O. The common pattern is to treat LDAP as the source of truth for credentials and your local User entity as the source of truth for application data: on successful bind, upsert the entity, stamp last_login_at, and let the provider return your entity on subsequent lookups (often via a ChainUserProvider with the local provider first). This also gives you somewhere to hang relations, audit rows, and app-specific roles that don't exist in the directory.

Group → role mapping. Map memberOf DNs to ROLE_* in the user factory, or do a second search under a groups base DN. Keep the mapping in config (a DN → role hash), not in code — directory group names are the sort of thing that changes without warning. Remember Symfony expects roles prefixed with ROLE_.

Environment parity. Use .env for everything: host, base DN, bind DN, filter. Run dev/staging against a small test directory (or a containerised osixia/openldap) so you aren't developing blind against production AD. Secrets go in Symfony's secrets vault or your platform's env, not in services.yaml.

Compose with other providers. ChainUserProvider (LDAP + a local ROLE_ADMIN break-glass account) is the standard escape hatch for when the directory is down or when you need a service account that isn't in AD.

Programmatic use. Because it's just a service, you can inject the provider into console commands and Messenger workers to resolve a username to a user without going through HTTP — useful for imports and impersonation tooling.

Testing. Unit-test the attribute mapping and role derivation directly; for functional tests, bind a stub provider in services_test.yaml rather than standing up an LDAP server. Keep one integration test against a real directory in CI, tagged and skipped by default.

Gotchas and Tips

  • Maintenance risk is the headline. Two stars, zero dependents, one maintainer. Pin an exact version in composer.json, read the source before upgrading, and assume you may have to fork. Apache-2.0 makes the fork legal and unencumbered, which is the main reason this is viable at all.
  • LDAP injection is real. Any user-supplied string going into a search filter must be escaped per RFC 4515, and anything going into a DN per RFC 4514. Don't roll your own str_replace — check whether the bundle sanitises, and if not, wrap it.
  • The empty-password trap. Many directories accept an unauthenticated bind when the password is empty, which some client stacks interpret as success. Explicitly reject zero-length passwords before the provider ever sees them.
  • AD specifics: default size limit of 1000 results (you need paged results if you ever enumerate), sAMAccountName vs userPrincipalName vs mail are all different things, and objectGUID is binary — convert to a string before storing it as an identifier. OpenLDAP will use uid/mail instead.
  • TLS: ldaps:// on 636 and StartTLS on 389 are not interchangeable; StartTLS must be issued before any bind. Certificate validation failures show up as a generic "can't contact" — check ldap.conf / the CA bundle before blaming the bundle.
  • Every request hits the network. refreshUser() fires on each authenticated request unless you cache or use the mirror pattern. Measure before shipping to a latency-sensitive page.
  • Credential erasure. If you mirror into an entity, implement __serialize/__unserialize (or rely on SensitiveParameter) so the password isn't written into the session — Symfony's eraseCredentials() won't save you from a careless serializer.
  • Debugging order: ldapsearch -x -H ldap://host -D 'binddn' -W -b 'base' '(uid=someone)' from the CLI first. If that fails, the bundle will only give you a vague exception. If it succeeds, move to bin/console debug:container <provider-service-id> to confirm wiring, then check that the firewall's provider: key matches the provider name (not the service ID) — a classic silent misconfiguration.
  • Extension points: the cleanest customisation is to decorate the user provider or supply your own user factory/entity rather than editing vendor code; both survive composer update. Overriding the service definition in your app's services.yaml is the pragmatic middle ground.
  • Expect thin docs. Budget half a day reading the bundle source. It's small enough that this is faster than searching for answers.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log