DirectoryTree / LdapRecord-Laravel

Multi-domain LDAP Authentication & Management for Laravel.
https://ldaprecord.com/docs/laravel/v3
MIT License
492 stars 51 forks source link

Login doesn't work when server is down even though sync_passwords option is set to true. #528

Closed A1exMiranda closed 1 year ago

A1exMiranda commented 1 year ago

Hi everyone, I hope you're doing well. I'm struggling a little bit trying to set the backup login when the LDAP (Active Directory) server is down on my work domain, login works fine when the server is up. I would like to know how to debug this, the only thing that I've seen is that on the db, the password column for users keeps hashing every time I do a users migration (I don't really know if that's how it should work).

The error I get when on my website when the AD server is down is "These credentials do not match our records".

---auth.php---

'providers' => [
        'users' => [
            'driver' => 'ldap',
            'model' => LdapRecord\Models\ActiveDirectory\User::class,
            'rules' => [],
            'database' => [
                'model' => App\Models\User::class,
                'sync_passwords' => true,
                'sync_attributes' => [
                    'name' => 'displayName',
                    'username' => 'cn',
                    'email' => 'mail',
                ],
            ],
        ],
    ],

---loginrequest.php---

public function rules(): array
    {
        return [
            'username' => ['required', 'string'],
            'password' => ['required', 'string'],
        ];
    }

public function authenticate(): void
    {
        $this->ensureIsNotRateLimited();

        $credentials = [
            'samaccountname' => $this->username,
            'password' => $this->password,
        ];

        if (! Auth::attempt($this->only('username', 'password'), $this->boolean('remember'))) {
            RateLimiter::hit($this->throttleKey());
            throw ValidationException::withMessages([
                'username' => trans('auth.failed'),
            ]);
        }
        RateLimiter::clear($this->throttleKey());
    }

---logincontroller.php---

protected function credentials(Request $request)
    {
        return [
            'cn' => $request->get('username'),
            'password' => $request->get('password'),
        ];
    }

    public function username()
    {
        return 'username';
    }

---laravel.log---

//Active Directory up [2023-05-02 10:13:35] local.INFO: LDAP (ldap://x.x.x.x:xxx) - Operation: Binding - [2023-05-02 10:13:36] local.INFO: LDAP (ldap://x.x.x.x:xxx) - Operation: Bound -
[2023-05-02 10:13:36] local.INFO: LDAP (ldap://x.x.x.x:xxx) - Operation: Search - Base DN: Time Elapsed: 222.65
[2023-05-02 10:13:36] local.INFO: User [alex] has been successfully discovered for authentication.
[2023-05-02 10:13:36] local.INFO: Object with name [alex] is being synchronized.
[2023-05-02 10:13:36] local.INFO: Object with name [alex] has been successfully synchronized.
[2023-05-02 10:13:36] local.INFO: User [alex] is authenticating.
[2023-05-02 10:13:36] local.INFO: LDAP (ldap://x.x.x.x:xxx) - Operation: Attempting -
[2023-05-02 10:13:36] local.INFO: LDAP (ldap://x.x.x.x:xxx) - Operation: Binding - [2023-05-02 10:13:36] local.INFO: LDAP (ldap://x.x.x.x:xxx) - Operation: Bound -
[2023-05-02 10:13:36] local.INFO: LDAP (ldap://x.x.x.x:xxx) - Operation: Passed - [2023-05-02 10:13:36] local.INFO: LDAP (ldap://x.x.x.x:xxx) - Operation: Binding - [2023-05-02 10:13:36] local.INFO: LDAP (ldap://x.x.x.x:xxx) - Operation: Bound - [2023-05-02 10:13:36] local.INFO: User [alex] has successfully passed LDAP authentication.
[2023-05-02 10:13:36] local.INFO: User [alex] has successfully authenticated.

//Active Directory down _[2023-05-02 10:14:14] local.INFO: LDAP (ldap://x.x.x.x:xxx) - Operation: Binding - [2023-05-02 10:14:19] local.WARNING: LDAP (ldap://x.x.x.x:xxx) - Operation: Failed - Reason: Can't contact LDAP server
[2023-05-02 10:14:24] local.ERROR: ldap_search(): Search: Can't contact LDAP server {"exception":"[object] (LdapRecord\LdapRecordException(code: 2): ldap_search(): Search: Can't contact LDAP server at /var/www/laravel/current/vendor/directorytree/ldaprecord/src/LdapRecordException.php:26) [stacktrace]

0 /var/www/laravel/current/vendor/directorytree/ldaprecord/src/HandlesConnection.php(184): LdapRecord\LdapRecordException::withDetailedError(Object(ErrorException), Object(LdapRecord\DetailedError))

[previous exception] [object] (ErrorException(code: 2): ldapsearch(): Search: Can't contact LDAP server at /var/www/laravel/current/vendor/directorytree/ldaprecord/src/Ldap.php:234) [stacktrace]

Thanks in advance!

stevebauman commented 1 year ago

Hi @A1exMiranda,

I'm not sure which starter stack you're using (Laravel UI, Jetstream, Breeze, etc), but you must configure fallback authentication, which is documented here:

https://ldaprecord.com/docs/laravel/v2/auth/database/laravel-ui/#fallback-authentication

This involves submitting a fallback array inside of the credentials sent through the Auth::attempt() method. LdapRecord will look at this array key to determine if it should catch any connection exceptions and attempt authenticating using Laravel's Eloquent driver:

$credentials = [
    'samaccountname' => $request->username,
    'password' => $request->password,
    'fallback' => $request->only('username', 'password'),
];

if (! Auth::attempt($credentials, $this->boolean('remember'))) {
    RateLimiter::hit($this->throttleKey());
    throw ValidationException::withMessages([
        'username' => trans('auth.failed'),
    ]);
}

Warning: Keep in mind, fallback only works if the user has successfully authenticated to your application before. Otherwise, their password would have not been synchronized, denying them authentication.

Also, it appears you're not sending the proper credentials array into the Auth::attempt() method in the code snippet you've posted.

This should resolve your issue! 👍