LDAP (Lightweight Directory Access Protocol) is an open protocol used for storing information about an organization and its users and assets. This protocol is used to communicate with a directory database to query, add or modify information.
LDAP authentication authenticates the user via LDAP server such as Active Directory. For such, user should have valid directory record in LDAP server to get authorized to access certain system or services.
Since LDAP is an open protocol, there are many different implementations available, the most prominent probably Microsoft Active Directory and OpenLDAP. We will use 389 Directory Service as the LDAP Server and the directory is under domain ldap.hanan.my.id as an example.
I’m using Fedora Cloud for installing the LDAP server, you may consider using other operating system you like. Simply install the 389ds package on Fedora: dnf install 389-ds-base
and will access it through Cockpit: dnf install cockpit-389-ds
.
A 389ds instance can be created through dscreate
from the inf file. Create the instance.inf
file with basis configuration:
[general] full_machine_name = ldap.hanan.my.id start = True [slapd] instance_name = localhost root_password = mysecret port = 389 secure_port = 636 self_sign_cert = False [backend-userroot] sample_entries = yes suffix = dc=ldap,dc=hanan,dc=my,dc=id
Change the highlighted lines (2,7,14) according to your configuration. Run command dscreate
for creating the instance: dscreate from-file instance.inf
During the installation a systemd
unit file called dirsrv@INSTANCE_NAME.service
is created and enabled so the instance is automatically started on boot. You may check the systemd
status:
~# systemctl status [email protected] ● [email protected] - 389 Directory Server localhost. Loaded: loaded (/usr/lib/systemd/system/[email protected]; enabled; preset: disabled) Drop-In: /usr/lib/systemd/system/service.d └─10-timeout-abort.conf /usr/lib/systemd/system/[email protected] └─custom.conf Active: active (running) since Sat 2024-01-27 15:02:08 UTC; 3min 22s ago Process: 11500 ExecStartPre=/usr/libexec/dirsrv/ds_systemd_ask_password_acl /etc/dirsrv/slapd-localhost/dse.ldif (code=exited, status=0/SUCCESS) Process: 11506 ExecStartPre=/usr/libexec/dirsrv/ds_selinux_restorecon.sh /etc/dirsrv/slapd-localhost/dse.ldif (code=exited, status=0/SUCCESS) Main PID: 11512 (ns-slapd) Status: "slapd started: Ready to process requests" Tasks: 29 (limit: 1087) Memory: 95.2M CPU: 1.054s CGroup: /system.slice/system-dirsrv.slice/[email protected] └─11512 /usr/sbin/ns-slapd -D /etc/dirsrv/slapd-localhost -i /run/dirsrv/slapd-localhost.pid
From now, we use Cockpit to manage 389ds, activate the cockpit
service: systemctl enable --now cockpit.socket
Let’s add a new User in with the basic information in the ou=people
tree:
For implementing LDAP authentication in Laravel, we pass user credentials to LDAP server for validation via authentication service. Further authorization is handled by Laravel itself after authentication.
I have a fresh installation of Laravel with Laravel UI authentication scaffolding. You should add the LdapRecord-Laravel library with composer: composer require directorytree/ldaprecord-laravel
and publish the configuration: php artisan vendor:publish --provider="LdapRecord\Laravel\LdapServiceProvider"
.
The ldap.php
file will then be created inside your applications config
, directory. You may set up inside the configuration file, or add some configuration in your .env
file:
LDAP_LOGGING=true LDAP_CONNECTION=default LDAP_HOST=ldap.hanan.my.id LDAP_USERNAME="cn=Directory Manager" LDAP_PASSWORD=mysecret LDAP_PORT=389 LDAP_BASE_DN="dc=ldap,dc=hanan,dc=my,dc=id" LDAP_TIMEOUT=5 LDAP_SSL=false LDAP_TLS=false LDAP_SASL=false
Once you have your connection configured, run a quick test to make sure they’ve been set up properly: php artisan ldap:test
You’re ready to start running queries and operations on your LDAP server. First thing I did was simply checking a user LDAP credentials are valid. Open php artisan tinker
and execute some command:
> $connection = LdapRecord\Container::getConnection('default'); > $connection->auth()->attempt('cn=Admin,ou=people,dc=ldap,dc=hanan,dc=my,dc=id', 'mysecret') true
You may create the LDAP modal via the ldap:make:model
command: php artisan ldap:make:model User
. This will create a new LdapRecord
model inside your application in the app/Ldap
folder. This is the example of my LdapRecord
model:
# app/Ldap/User.php <?php namespace App\Ldap; use LdapRecord\Models\Model; use LdapRecord\Models\Concerns\CanAuthenticate; use Illuminate\Contracts\Auth\Authenticatable; use LdapRecord\Query\Model\Builder; class User extends Model implements Authenticatable { use CanAuthenticate; /** * The object classes of the LDAP model. */ public static array $objectClasses = [ "nsPerson", "nsAccount" ]; protected string $guidKey = 'entryUUID'; protected static function boot(): void { parent::boot(); static::addGlobalScope('people', function (Builder $builder) { $builder->in('ou=people,{base}'); }); } }
To begin querying your model, you can statically call query methods off of the model:
use App\Ldap\User; .. User::where('mail', '[email protected]')->first();
In the boot()
method, the scope is added to utilize query filters in the organizational unit ou=people
. Any search queries that are performed on User model will be properly scoped. Meaning when you search '[email protected]'
, it will perform search in scope of ou=people
only.
The new object also can be created from the User model and save it into the directory:
$ldapUser = new User([ 'cn'=>'Eve', 'displayName'=>'Eve', 'mail'=>'[email protected]', 'userPassword'=>'mysecret' ]); $ldapUser->inside('ou=people,dc=ldap,dc=hanan,dc=my,dc=id'); $ldapUser->save()
There are two ways to authenticate LDAP users into Laravel application. If you have an application that doesn’t require any user data to be synchronized to your database, then you can utilize plain LDAP authentication. Otherwise, you may use the synchronized database LDAP Authentication method, meaning that an LDAP user which successfully passes LDAP authentication will be created & synchronized to your local application’s database. In my case, I chose the second option.
Let’s configure the authentication provider inside config/auth.php
:
'providers' => [ ... 'users' => [ 'driver' => 'ldap', 'model' => App\Ldap\User, 'rules' => [], 'scopes' => [], 'database' => [ 'model' => App\Models\User::class, 'sync_passwords' => true, 'sync_attributes' => [ 'name' => 'cn', 'email' => 'mail', ], 'sync_existing' => [ 'email' => 'mail', ], ], ... ],
For this example application, we will authenticate our LDAP users with their email address using the LDAP attribute mail
. Override the credentials
method in the LoginController
:
// app/Http/Controllers/Auth/LoginController.php use Illuminate\Http\Request; protected function credentials(Request $request) { return [ 'mail' => $request->email, 'password' => $request->password, ]; }
If a user attempts to log in with the above email address and this user does not exist inside your LDAP directory, then standard Eloquent authentication will be performed instead.
The sync_passwords
option, the sync_attributes
array and the sync_existing
array in the authentication provider inside config/auth.php
make the users
database table to be synchronized when users successfully pass LDAP authentication.
Further information: