Customizing Filament’s Login to use LDAP
This is a quick rough draft post for how to override the default Filament login and use your own to login with LDAP.
Tested With:
- Laravel 9
- Filament 2
- ldaprecord-laravel 2.6
Notes:
I’m using Filament 2 but it should work the same in 3. But check the code and refer to the docs if you run into trouble. There’s not a lot of super custom code here so this should work with updated versions of all the packages used.
I’m using ldaprecord-laravel package 2.x in this example as well. In my use case I only needed to authenticate against our LDAP login. Using ldaprecord may be overkill in my use case but I expected there to be more requirements at some point. If you need to sync anything from the LDAP user model ldaprecord will let you do that.
Could we do this with just some event listener or other way instead of overriding the Filament Login.php? That would be better so we don’t have to keep our version of Login.php current with any changes to the standard one provided by Filament. I never got a chance to look into this but it seems like there would be a way. Feel free to shout in the comments if you know. I’m using the directorytree/ldaprecord-laravel package in this code, so you’ll need to:
1composer require directorytree/ldaprecord-laravel
Make sure to go star the repo. Steve is a really sharp developer and all around nice guy.
Follow the instructions to setup your LDAP configuration in Laravel. It’s pretty standard Laravel stuff here. Make sure to configure your ldap connection per the instructions for this package. We’ll be using these variables to connect and verify that the user provided valid credentials.
Then you just need to override the default login by creating our own here:
1app/Filament/Pages/Auth/Login.php
Below is what this code looks like. It’s a combination of the default login from Filament with the ldap stuff doing the job of validating the creds, then we just log the user in the standard Laravel way.
Below is the code, commented for clarity.
That should do it. Let me know if I missed anything or if you have suggestions for improvement.
1<?php 2namespace App\Filament\Pages\Auth; 3 4use App\Models\User; 5use Exception; 6use Filament\Forms\Components\Checkbox; 7use Filament\Forms\Components\TextInput; 8use Filament\Forms\Components\ViewField; 9use Filament\Forms\Contracts\HasForms; 10use Filament\Http\Livewire\Auth\Login as BasePage; 11use Filament\Http\Responses\Auth\Contracts\LoginResponse; 12use Illuminate\Support\Facades\Auth; 13use Illuminate\Support\Facades\Hash; 14use Illuminate\Support\Str; 15use LdapRecord\Connection; 16use LdapRecord\Container; 17 18class Login extends BasePage implements HasForms { 19 20 public $username = ''; 21 public $password = ''; 22 public $remember = false; 23 24 public function authenticate() : ?LoginResponse { 25 // Implement rate limiting to protect against brute force attacks 26 try { 27 $this->rateLimit(5); 28 } catch (TooManyRequestsException $exception) { 29 $this->addError('username', __('filament::login.messages.throttled', [ 30 'seconds' => $exception->secondsUntilAvailable, 31 'minutes' => ceil($exception->secondsUntilAvailable / 60), 32 ])); 33 return null; 34 } 35 36 $data = $this->form->getState(); 37 38 // Prefix for LDAP user names (corp\\username) 39 $ldap_user = 'corp' . '\\' . $data['username']; 40 $ldap_pass = $data['password']; 41 42 // Use environment variables for LDAP configuration 43 $connection = new Connection([ 44 'hosts' => [config('LDAP_HOST', 'your_default_host')], 45 'port' => config('LDAP_PORT', 389), 46 'base_dn' => config('LDAP_BASE_DN', 'DC=corp,DC=your_domain,DC=com'), 47 'username' => $ldap_user, 48 'password' => $ldap_pass, 49 ]); 50 51 Container::addConnection($connection); 52 53 // Attempt LDAP authentication 54 try { 55 $connection->auth()->attempt($ldap_user, $ldap_pass); 56 } catch (Exception $e) { 57 // Provide a more specific error message for authentication failure 58 $this->addError('username', __('Authentication failed, please try again.')); 59 $this->addError('password', __('Authentication failed, please try again.')); 60 return null; 61 } 62 63 if (get_class($connection) === Connection::class) { 64 $query = $connection->query(); 65 // Retrieve LDAP record, add any additional logic you want to check for a valid/active user here 66 $ldap_record = $query->where('your_ldap_account_field_name', '=', $data['username'])->first(); 67 68 69 // Create or update the local user 70 $user = User::updateOrCreate([ 71 'email' => $ldap_record['your_ldap_email_field_name'][0], 72 ], [ 73 'name' => $ldap_record['cn'][0], 74 'password' => Hash::make(Str::random(10)), // Hash a random string for password 75 ]); 76 77 Auth::login($user, $this->remember); 78 79 // Additional steps for user model or login process can be added here 80 81 // Return the expected LoginResponse class 82 return app(LoginResponse::class); 83 } 84 85 return null; 86 } 87 88 protected function getFormSchema() : array { 89 return [ 90 ViewField::make('login-notice')->view('filament.pages.auth.login.header'), 91 TextInput::make('username') 92 ->label(__('Username')) 93 ->required() 94 ->autocomplete(), 95 TextInput::make('password') 96 ->label(__('filament::login.fields.password.label')) 97 ->password() 98 ->required(), 99 Checkbox::make('remember')100 ->label(__('filament::login.fields.remember.label')),101 ];102 }103 104}
In the above code you could implement the user sync from ldap to your local database. That was not a requirement for my use case. But it’s documented in the ldaprecord package if you need that
Good afternoon Chrispian - I realize this is an old post, but I am in the process of setting up a new Filament powered Laravel app and I am having a difficult time trying to get LDAP auth to work. I followed your steps above, but the default authentication page is still rendering instead of the customized login page that I created. I am indeed using Filament 3 for reference, so I am not sure if that may be part of the issue. I have reviewed all of the documentation I can find and I can't seem to come up with anything that works. Thanks ! -Jonathan
Hi Jonathan, I've recently implemented LDAP with Filament 3 and it was much simpler than expected. My use-case needed very minimal change however. I installed LDAP as per the docs and then I extended BaseAuth using a custom Login.php class and only needed to override the function
getCredentialsFromFormData()
to change the array key "email" to "mail". Everything else remains the same.sorry, forgot to mention the line to tell the filament panel to load the extension to the default login class. put this onto your "...PanelProvider.php"
->login(Login::class)
be sure to import the correct class! There can be a few conflicts here.Perfect! Thank you for your response. This got me headed in the right direction and everything is working now! -Jonathan