Filament
Laravel Filament Tutorial – Customize Login to use LDAP

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

No comments yet…

avatar
You can use Markdown
No results