Updated June 2026. Tested on Laravel 13 and PHP 8.4. Modern Laravel auth uses Fortify (under Jetstream) or Breeze, so this shows the Fortify approach. The old AuthenticatesUsers trait from laravel/ui is retired.
A nice touch for users is one login field that accepts either their username or their email, instead of forcing them to remember which they signed up with. Modern Laravel makes this clean through Fortify's authenticateUsing hook.
This assumes your users table has a username column alongside email.
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('username')->unique();
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
The login field
In your login form, use one field for both. Name it something neutral like login.
<label for="login">Username or Email</label>
<input id="login" name="login" type="text" value="{{ old('login') }}" required autofocus>
Tell Fortify how to authenticate
In App\Providers\FortifyServiceProvider, use Fortify::authenticateUsing. You decide whether the input is an email or a username, look the user up on the right column, and check the password.
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Laravel\Fortify\Fortify;
public function boot(): void
{
Fortify::authenticateUsing(function ($request) {
$login = $request->input('login');
$field = filter_var($login, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
$user = User::where($field, $login)->first();
if ($user && Hash::check($request->input('password'), $user->password)) {
return $user;
}
});
}
The key line is the filter_var check. If the input looks like an email, we search the email column, otherwise we search username. Then a normal Hash::check verifies the password. Returning the user logs them in; returning nothing fails the attempt, and Fortify handles the error response and rate limiting for you.
If you use Breeze instead
Breeze does not use Fortify, so the place to change is the LoginRequest. In its authenticate method, build the credentials with the same email or username decision before calling Auth::attempt.
$login = $this->input('login');
$field = filter_var($login, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
if (! Auth::attempt([$field => $login, 'password' => $this->input('password')], $this->boolean('remember'))) {
// throw the validation error as Breeze already does
}
Either way the idea is the same: detect whether the input is an email or a username, then authenticate against the matching column. One field, no friction for the user. Questions welcome in the comments.
All comments ()
No comments yet
Be the first to leave a comment on this post.