Laravel Jetstream
Introduction
Important: Before getting started, please complete the configuration guide.
Laravel Jetstream utilizes Laravel Fortify for authentication under the hood. We will customize various aspects of it to allow LDAP users to sign into the application.
Fortify Setup
Authentication Callback
For this example application, we will authenticate our LDAP users
with their email address using the LDAP attribute mail
.
For LdapRecord to properly locate the user in your directory during sign in, we will
override Fortify's authentication callback using the Fortify::authenticateUsing()
method in our AuthServiceProvider.php
file:
// app/Providers/AuthServiceProvider.php
// ...
use Laravel\Fortify\Fortify;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
// ...
public function boot()
{
$this->registerPolicies();
Fortify::authenticateUsing(function ($request) {
$validated = Auth::validate([
'mail' => $request->email,
'password' => $request->password
]);
return $validated ? Auth::getLastAttempted() : null;
});
}
}
As you can see above, we set the mail
key which is passed to the LdapRecord authentication provider.
A search query will be executed on your LDAP directory for a user that contains the mail
attribute equal
to the entered email
that the user has submitted on your login form. The password
key will not be used in the search.
If a user cannot be located in your directory, or they fail authentication, they will be redirected to the login page normally with the "Invalid credentials" error message.
You may also add extra key => value pairs in the
credentials
array to further scope the LDAP query. Thepassword
key is automatically ignored by LdapRecord.
Feature Configuration
Since we are synchronizing data from our LDAP server, we must disable the following
features by commenting them out inside of the config/fortify.php
file:
// config/fortify.php
// Before:
'features' => [
Features::registration(),
Features::resetPasswords(),
// Features::emailVerification(),
Features::updateProfileInformation(),
Features::updatePasswords(),
// Features::twoFactorAuthentication(),
],
// After:
'features' => [
// Features::registration(),
// Features::resetPasswords(),
// Features::emailVerification(),
// Features::updateProfileInformation(),
// Features::updatePasswords(),
// Features::twoFactorAuthentication(),
],
Important: You may keep
Features::registration()
enabled if you would like to continue accepting local application user registration. Keep in mind, if you continue to allow registration, you will need to either use multiple Laravel authentication guards, or setup the login fallback feature.
Sessions
If you are using the database
session driver, you must change the user_id
column from its default type. This is due to LDAP Object GUID's being stored
as the user's ID, which is not compatible with the unsigned big integer type:
From:
public function up()
{
Schema::create('sessions', function (Blueprint $table) {
// ...
$table->foreignId('user_id')->nullable()->index();
// ...
});
}
To:
public function up()
{
Schema::create('sessions', function (Blueprint $table) {
// ...
$table->uuid('user_id')->nullable()->index();
// ...
});
}
Using Usernames
To authenticate your users by their username we must adjust some scaffolded code generated by Laravel Jetstream.
In the following example, we will authenticate users by their sAMAccountName
.
Fortify Setup
Authentication Callback
With our Fortiy configuration updated, we will jump into our AuthServiceProvider.php
file
and setup our authentication callback using the Fortify::authenticateUsing()
method:
// app/Providers/AuthServiceProvider.php
// ...
use Laravel\Fortify\Fortify;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
// ...
public function boot()
{
$this->registerPolicies();
Fortify::authenticateUsing(function ($request) {
$validated = Auth::validate([
'samaccountname' => $request->username,
'password' => $request->password
]);
return $validated ? Auth::getLastAttempted() : null;
});
}
}
Username Configuration
Inside of our config/fortify.php
file, we must change the username
option to username
from email
:
// config/fortify.php
// Before:
'username' => 'email',
// After:
'username' => 'username',
You will notice above that we are passing in an array of credentials with
samaccountname
as the key, and the requests username
form input.
Login View
Now we must open up the login.blade.php
view and swap the current HTML input field from email
to username
so we can retrieve it properly in our Fortify::authenticateUsing()
callback:
<!-- Before: -->
<div>
<x-jet-label value="Email" />
<x-jet-input
class="block w-full mt-1"
type="email"
name="email"
:value="old('email')"
required
autofocus
/>
</div>
<!-- After: -->
<div>
<x-jet-label value="Username" />
<x-jet-input
class="block w-full mt-1"
type="text"
name="username"
:value="old('username')"
required
autofocus
/>
</div>
Displaying LDAP Error Messages
When using Laravel Jetstream, LDAP error messages will now be displayed automatically
to users. You do not need to configure or include the ListensForLdapBindFailure
trait as you would using Laravel UI
on the LoginController
.
Altering the Response
Since this functionality is now automatically registered, if you would like to modify how
an error is handled, call the setErrorHandler
method on the BindFailureListener
class inside of your AuthServiceProvider.php
file:
// app/Providers/AuthServiceProvider.php
// ...
use LdapRecord\Laravel\Auth\BindFailureListener;
class AuthServiceProvider extends ServiceProvider
{
// ...
public function boot()
{
$this->registerPolicies();
BindFailureListener::setErrorHandler(function ($message, $code = null) {
if ($code == '773') {
// The users password has expired. Redirect them.
abort(redirect('/password-reset'));
}
});
}
}
Refer to the Password Policy Errors documentation to see what each code means.
Changing the Error Messages
If you need to modify the translations of these error messages, create a new translation
file named errors.php
in your resources
directory at the following path:
The
vendor
directory (and each sub-directory) will have to be created manually.
Laravel >= 9
lang/
└── vendor/
└── ldap/
└── en/
└── errors.php
Laravel <= 8
resources/
└── lang/
└── vendor/
└── ldap/
└── en/
└── errors.php
Then, paste in the following translations in the file and modify where necessary:
<?php
return [
'user_not_found' => 'User not found.',
'user_not_permitted_at_this_time' => 'Not permitted to logon at this time.',
'user_not_permitted_to_login' => 'Not permitted to logon at this workstation.',
'password_expired' => 'Your password has expired.',
'account_disabled' => 'Your account is disabled.',
'account_expired' => 'Your account has expired.',
'user_must_reset_password' => 'You must reset your password before logging in.',
'user_account_locked' => 'Your account is locked.',
];