Database Auth Configuration
Introduction
To configure a synchronized database LDAP authentication provider, navigate
to the providers
array inside your config/auth.php
file, and paste
the following users
provider:
You will have to remove/alter the default
users
provider, or create your own.
// config/auth.php
'providers' => [
// ...
'users' => [
'driver' => 'ldap',
'model' => LdapRecord\Models\ActiveDirectory\User::class,
'rules' => [],
'scopes' => [],
'database' => [
'model' => App\Models\User::class,
'sync_passwords' => false,
'sync_attributes' => [
'name' => 'cn',
'email' => 'mail',
],
],
],
],
As you can see above, a database
array is used to configure the association between your LDAP user and your Eloquent user.
Important:
If your application requires more than one LDAP connection, you must create a new provider for each connection.
This new provider must have its own uniquemodel
class which must use your alternate configured connection name using the$connection
property.
In the scenario of having multiple LDAP connections, it may be helpful to namespace the LDAP models you create with the desired connection. For example:
App\Ldap\DomainAlpha\User
This will allow you to segregate scopes, rules and other classes to their relating connection.
Driver
The driver
option must be ldap
as this is what indicates to Laravel the proper authentication driver to use.
Model
The model
option must be the class name of your LdapRecord model. This model will be used
for fetching users from your directory.
Rules
The rules
option must be an array of authentication rule class name's.
Overview
LDAP authentication rules give you the ability to allow or deny users from signing in to your application using a condition you would like to apply. These rules are executed after a user successfully passes LDAP authentication against your configured server.
Think of them as a final authorization gate before they are allowed in.
Authentication rules are never executed if a user fails LDAP authentication.
Creating Rules
Let's create an LDAP rule that only allows members of our domain Administrators
group.
To create an authentication rule, call the ldap:make:rule
command:
php artisan ldap:make:rule OnlyAdministrators
A rule will then be created in your applications app/Ldap/Rules
directory:
<?php
namespace App\Ldap\Rules;
use LdapRecord\Laravel\Auth\Rule;
use LdapRecord\Models\Model as LdapRecord;
use Illuminate\Database\Eloquent\Model as Eloquent;
class OnlyAdministrators implements Rule
{
/**
* Check if the rule passes validation.
*/
public function passes(LdapRecord $user, Eloquent $model = null): bool
{
//
}
}
In the authentication rule, there are two properties made available to us.
- A
$user
property that is the LdapRecord model of the authenticating user - A
$model
property that is the Eloquent model of the authenticating user
Now, we will update the passes
method to check the LDAP users groups
relationship to see if they are a member:
<?php
namespace App\Ldap\Rules;
use LdapRecord\Laravel\Auth\Rule;
use LdapRecord\Models\Model as LdapRecord;
use LdapRecord\Models\ActiveDirectory\Group;
use Illuminate\Database\Eloquent\Model as Eloquent;
class OnlyAdministrators implements Rule
{
public function passes(LdapRecord $user, Eloquent $model = null): bool
{
$administrators = Group::find('cn=Administrators,dc=local,dc=com');
return $user->groups()->recursive()->exists($administrators);
}
}
We call the
recursive
method on the relationship to make sure that we load groups of groups in case the user is not an immediate member of theAdministrators
group.
Once we have our rule defined, we will add it into our authentication provider in the config/auth.php
file:
'providers' => [
// ...
'users' => [
'driver' => 'ldap',
'model' => LdapRecord\Models\ActiveDirectory\User::class,
'rules' => [
App\Ldap\Rules\OnlyAdministrators::class,
],
'database' => [
// ...
],
],
],
Now when you attempt to log in to your application with an LDAP user that successfully passes
LDAP authentication, they will need to be a member of the Administrators
group.
If you are caching your configuration, make sure you re-run
config:cache
to re-cache your modifications.
Scopes
The scopes
option must be an array of LdapRecord scope class names.
Overview
The scopes
inserted in this option allow you to apply query scopes to your configured model,
only during import and authentication. This is option is useful for when you don't want to
add global scopes to your configured model, but would like to scope the query used to
retrieve users during import and authentication into your application.
Creating Scopes
Let's create an LDAP scope that scopes the authentication query to only return
users that are located inside the Accounting
Organizational Unit.
To create a new LDAP scope, call the ldap:make:scope
command:
php artisan ldap:make:scope Accounting
A scope will then be created in your applications app/Ldap/Scopes
directory:
<?php
namespace App\Ldap\Scopes;
use LdapRecord\Models\Model;
use LdapRecord\Models\Scope;
use LdapRecord\Query\Model\Builder;
class Accounting implements Scope
{
/**
* Apply the scope to the given query.
*/
public function apply(Builder $query, Model $model): void
{
// ...
}
}
Now let's update the apply
method to only return users located inside the Accounting
Organizational Unit:
We're using automatic base DN subsitution in query below by supplying
{base}
in the$query->in()
method.
class Accounting implements Scope
{
/**
* Apply the scope to the given query.
*/
public function apply(Builder $query, Model $model): void
{
$query->in('ou=Accounting,{base}');
}
}
Once we have our scope defined, we will add it into our authentication provider in the config/auth.php
file:
'providers' => [
// ...
'users' => [
'driver' => 'ldap',
'model' => LdapRecord\Models\ActiveDirectory\User::class,
'rules' => [],
'scopes' => [
App\Ldap\Scopes\Accounting::class,
],
],
],
Now when you attempt to log in to your application with an LDAP user, that LDAP
user must be located inside the Accounting
Organizational Unit to be able
to authenticate into your application.
If you are caching your configuration, make sure you re-run
config:cache
to re-cache your modifications.
Database Model
The database => model
key is the class name of the Eloquent model that will be
used for creating and retrieving LDAP users from your applications database.
Be sure to add the required trait and interface to this model as shown in the installation guide.
Sync Password Column
If your application uses a different password column than password
, then you
may configure it using the below methods depending on your Laravel version.
Laravel >= 11
In your User
Eloquent model, define a getAuthPasswordName
method that returns the name of your password column:
// app/Models/User.php
class User extends Authenticatable
{
public function getAuthPasswordName(): string
{
return 'my_password_column';
}
}
Laravel < 11
In your config/auth.php
file, set the password_column
key to the name of your password column:
'providers' => [
// ...
'users' => [
// ...
'database' => [
// ...
'password_column' => 'my_password_column',
],
],
],
Remove Password Column
If your users
table does not have a password column, you must use the config/auth.php
file method regardless of Laravel version and set the password_column
to false
:
'providers' => [
// ...
'users' => [
// ...
'database' => [
// ...
'password_column' => false,
],
],
],
'providers' => [
// ...
'users' => [
// ...
'database' => [
// ...
'password_column' => 'my_password_column',
],
],
],
You can also set the value to false
if your database table does not have any password column at all:
'providers' => [
// ...
'users' => [
// ...
'database' => [
// ...
'password_column' => false,
],
],
],
Sync Passwords
The database => sync_passwords
option enables password synchronization.
Password synchronization captures and hashes the users password upon login if they pass LDAP authentication. This helps in situations where you may want to provide a "back up" option in case your LDAP server is unreachable, as well as a way of determining if a users password is valid without having to call to your LDAP server and validate it for you.
If you do not define the
sync_passwords
key or have it setfalse
, a user is always applied a random 16 character hashed password. This hashed password is only set once upon initial import or login so no needless updates are performed on user records.
Sync Attributes
The database => sync_attributes
array defines a set of key-value pairs that
describe which database column should be set and to which LDAP property:
'sync_attributes' => [
'email' => 'mail',
'name' => 'cn',
]
- The key of each array item is the attribute of your
User
Eloquent model - The value is the name of the users LDAP attribute to set the Eloquent model attribute value to
You do not need to add your users
guid
ordomain
database columns. These are done automatically for you.
For further control on sync attributes, see the below attribute handler feature.
Sync Existing Records
The database => sync_existing
array defines a set of key-value pairs that
describe how existing database users should be sychronized:
'sync_existing' => [
'email' => 'mail',
],
- The key of each array item is the column of your
users
database table to query - The value is the name of the users LDAP attribute to set the database value to
Alternatively inside each
value
key, you may provide an array with anattribute
key containing the LDAP attribute name and anoperator
key to use for the query when retrieving a record from the database. More on this below.Important: If the LDAP attribute returns
null
for the given value, the actual value will be used in the query instead. This is helpful to be able to use raw strings to scope your query by.
Let's walk through an example.
In our application, we have existing users inside our database:
id | name | password | guid | domain | |
---|---|---|---|---|---|
1 | Steve Bauman | sbauman@local.com | ... | null |
null |
2 | John Doe | jdoe@local.com | ... | null |
null |
As you can see above, these users have null
values for their guid
and domain
columns.
If you do not define a sync_existing
array and a user logs in with sbauman@local.com
,
you will receive a SQL exception. This is because LdapRecord was unable to locate a local
database user using the users GUID. If this occurs, LdapRecord will attempt to insert a
new user with the same email address.
To resolve this issue, we will insert the following sync_existing
array:
'providers' => [
// ...
'users' => [
// ...
'database' => [
// ...
'sync_existing' => [
'email' => 'mail',
],
],
],
],
Now when sbauman@local.com
attempts to log in, if the user cannot be located
by their GUID, they will instead be located by their email address. Their
GUID, domain, and sync attributes you define will then synchronize.
Database Compatibility
In some database drivers, such as Postgres, there is case-sensitivity when executing where
clauses with the equals (=
) operator. Consider the following data in your database:
id | name | password | guid | domain | |
---|---|---|---|---|---|
1 | Steve Bauman | sbauman@local.com | ... | null |
null |
2 | John Doe | jdoe@local.com | ... | null |
null |
However, inside the LDAP server, the mail
attribute for Steve's record
is actually SBauman@local.com
. While he could successfully authenticate,
the existing record would not be found in our database due to Postgres'
more strict SQL grammar. Changing the sync_existing
configuration
to the following array syntax would allow us to change the
operator from an equals (=
) to an ilike
.
'sync_existing' => [
'email' => [
'attribute' => 'mail',
'operator' => 'ilike',
],
],
By replacing the value of the array to be an array with the attribute
and operator
keys, we can fine-tune the query syntax to be more flexible to your needs.
Attribute Handlers
If you require logic for synchronizing attributes when users sign in to your application or are being imported, you can create an attribute handler class responsible for setting / synchronizing your database models attributes from their LDAP model.
This class you define must have either a handle
or __invoke
method. This method must accept the LDAP model you
have configured as the first parameter and your Eloquent database model as the second.
For the example below, we will create a handler named AttributeHandler.php
inside your app/Ldap
folder:
You do not need to call
save()
on your Eloquent database model. This is called for you after attribute synchronization.
<?php
namespace App\Ldap;
use App\Models\User as DatabaseUser;
use App\Ldap\User as LdapUser;
class AttributeHandler
{
public function handle(LdapUser $ldap, DatabaseUser $database)
{
$database->name = $ldap->getFirstAttribute('cn');
$database->email = $ldap->getFirstAttribute('mail');
}
}
Attribute handlers are created using Laravel's
app()
helper, so you may type-hint any dependencies you require in your handlers constructor to be made available during synchronization.
Then inside your config/auth.php
file for your provider, set the attribute handler class as the sync_attributes
value:
'providers' => [
// ...
'users' => [
// ...
'database' => [
// ...
'sync_attributes' => \App\Ldap\AttributeHandler::class,
],
],
],
You may also add multiple if you'd prefer, or combine them with key => value
pairs:
// ...
'database' => [
// ...
'sync_attributes' => [
'name' => 'cn',
'email' => 'mail',
\App\Ldap\MyFirstAttributeHandler::class,
\App\Ldap\MySecondAttributeHandler::class,
],
],
Attributes you specify are synchronized in order (first to last), so you may access the already synchronized attributes in subsequent attribute handlers.
All Available Options
Below is a synchronized database provider that is configured with all available options:
// config/auth.php
'providers' => [
// ...
'users' => [
'driver' => 'ldap',
'model' => LdapRecord\Models\ActiveDirectory\User::class,
'rules' => [],
'scopes' => [],
'database' => [
'model' => App\Models\User::class,
'sync_passwords' => true,
'sync_attributes' => [
'name' => 'cn',
'email' => 'mail',
],
'sync_existing' => [
'email' => 'mail',
],
'password_column' => 'password',
],
],
],