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:

// 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.

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.

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);
    }
}

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.

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:

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.

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.

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.

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

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

Let's walk through an example.

In our application, we have existing users inside our database:

idnameemailpasswordguiddomain
1Steve Bauman[email protected]...nullnull
2John Doe[email protected]...nullnull

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 [email protected], 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 [email protected] 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:

idnameemailpasswordguiddomain
1Steve Bauman[email protected]...nullnull
2John Doe[email protected]...nullnull

However, inside the LDAP server, the mail attribute for Steve's record is actually [email protected]. 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:

<?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');
    }
}

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,
    ],
],

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',
        ],
    ],
],