DirectoryTree / LdapRecord-Laravel

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

Displaying LDAP Error Messages does not work. #596

Closed anchan42 closed 1 year ago

anchan42 commented 1 year ago

Environment:

I followed all the settings in the documentation but when I tried changing the LDAP server settings to non-existence server, the error still show 'These credentials do not match our records.' instead of 'Can't connect to LDAP server'

stevebauman commented 1 year ago

Hi @anchan42,

Can you link the documentation you followed?

anchan42 commented 1 year ago

Hi Steve,

It this one ldap-error-messages

stevebauman commented 1 year ago

Thanks @anchan42, can you post your code as well?

anchan42 commented 1 year ago

Sorry, Steve. I think posted this in the wrong repo. It should have been the one for Laravel.

Please see the code below:

AuthenticatedSessionController:

<?php

namespace App\Http\Controllers\Auth;

use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Routing\RouteUri;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\RedirectResponse;
use App\Providers\RouteServiceProvider;
use App\Http\Requests\Auth\LoginRequest;
use LdapRecord\Laravel\Auth\ListensForLdapBindFailure;

class AuthenticatedSessionController extends Controller
{
    /**
     * Display the login view.
     */
    use ListensForLdapBindFailure;

    protected $username = 'username';

    public function __construct()
    {
        $this->listenForLdapBindFailure();
    }

    public function create(): View
    {
        return view('auth.login');
    }

    /**
     * Handle an incoming authentication request.
     */
    public function store(LoginRequest $request): RedirectResponse
    {
        $request->authenticate();

        $request->session()->regenerate();

        return redirect()->intended(route('bd_reports.index',['status' => '1']));

    }

    /**
     * Destroy an authenticated session.
     */
    public function destroy(Request $request): RedirectResponse
    {
        Auth::guard('web')->logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        return redirect('/');
    }
}

LoginRequest:

<?php

namespace App\Http\Requests\Auth;

use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class LoginRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
     */
    public function rules(): array
    {
        return [
            'username' => ['required', 'string'],
            'password' => ['required', 'string'],
        ];
    }

    /**
     * Attempt to authenticate the request's credentials.
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function authenticate(): void
    {
        $this->ensureIsNotRateLimited();

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

        if (!Auth::attempt($credentials, $this->filled('remember'))) {
            RateLimiter::hit($this->throttleKey());

            throw ValidationException::withMessages([
                'username' => __('auth.failed'),
            ]);
        }

        RateLimiter::clear($this->throttleKey());
    }

    public function ensureIsNotRateLimited(): void
    {
        if (!RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
            return;
        }

        event(new Lockout($this));

        $seconds = RateLimiter::availableIn($this->throttleKey());

        throw ValidationException::withMessages([
            'username' => trans('auth.throttle', [
                'seconds' => $seconds,
                'minutes' => ceil($seconds / 60),
            ]),
        ]);
    }

    /**
     * Get the rate limiting throttle key for the request.
     */
    public function throttleKey(): string
    {
        return Str::transliterate(Str::lower($this->input('email')) . '|' . $this->ip());
    }
}
stevebauman commented 1 year ago

No worries @anchan42, I've transferred the issue to the Laravel repo 👍

You're not seeing the exception due to configuring fallback authentication. When you add fallback authentication (by passing in the fallback array in the user's credentials) any connection exceptions are caught so that LdapRecord-Laravel can fall back to the database and attempt to perform Eloquent authentication. This is described here, but I can update the language to be more explicit:

https://ldaprecord.com/docs/laravel/v3/auth/database/laravel-breeze/#fallback-authentication

Database fallback allows the authentication of local database users if:

  • LDAP connectivity is not present.
  • Or; An LDAP user cannot be found.

Since fallback sends the authentication request to Eloquent, a typical Eloquent "authentication failed" validation message will appear.

For failed LDAP error messages to appear, you cannot have fallback configured. Try removing the fallback key from your credentials and then also call LdapRecord::failLoudly() in your controller constructor:

use LdapRecord\Laravel\LdapRecord

class AuthenticatedSessionController extends Controller
{
    /**
     * Display the login view.
     */
    use ListensForLdapBindFailure;

    protected $username = 'username';

    public function __construct()
    {
        LdapRecord::failLoudly();

        $this->listenForLdapBindFailure();
    }

    // ...
}

Once you've done the above, try authenticating again and you should see the connection error message appear 👍

anchan42 commented 1 year ago

Ok I understand now. Is there any way I can revert the auth order? i.e. using LDAP as a fallback when database auth fails?

stevebauman commented 1 year ago

No not with LdapRecord-Laravel. You would have to build your own fallback logic with separate auth guards (one for Eloquent and one for LdapRecord), attempt authentication with the Eloquent guard, then fallback to LdapRecord if it fails 👍

Going to close this as your question has been resolved, let me know if otherwise!

anchan42 commented 1 year ago

Thanks, Steve 👍