Laravel provides a robust authentication system right out of the box. However, in many real-world scenarios, you may need to customize or extend this functionality to suit your application’s unique requirements. In this article, we’ll walk through the steps to customize Laravel’s authentication system, including creating a custom login system, modifying user credentials, and enhancing security.
1. Understanding Laravel’s Default Authentication
Laravel’s default authentication system includes a set of routes, controllers, and views that make it easy to manage user authentication. By running php artisan make:auth
(in Laravel versions before 8), or manually configuring routes in Laravel 8 and above, you can generate the necessary login, registration, and password reset functionality.
However, to build a custom login system, we will go beyond these defaults, allowing us to:
- Use custom fields for login (e.g.,
username
instead ofemail
). - Modify authentication logic.
- Add additional security layers (e.g., two-factor authentication).
2. Setting Up the Authentication Routes
The first step in creating a custom login system is setting up your routes. You can define custom authentication routes in the routes/web.php
file:
use App\Http\Controllers\Auth\LoginController;
Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
Route::post('login', [LoginController::class, 'login']);
Route::post('logout', [LoginController::class, 'logout'])->name('logout');
Here, we define routes for showing the login form, processing login attempts, and logging users out.
3. Creating a Custom Login Controller
Next, you will need to create a custom LoginController
to handle the authentication logic. You can start by generating the controller:
php artisan make:controller Auth/LoginController
In your LoginController.php
, you can customize the login behavior as follows:
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
public function showLoginForm()
{
return view('auth.login');
}
public function login(Request $request)
{
// Custom validation logic
$credentials = $request->validate([
'username' => 'required|string',
'password' => 'required|string',
]);
// Attempt to log the user in with custom credentials
if (Auth::attempt($credentials)) {
// Authentication successful, redirect to intended page
return redirect()->intended('dashboard');
}
// If login fails, redirect back with error
return back()->withErrors([
'username' => 'The provided credentials do not match our records.',
]);
}
public function logout()
{
Auth::logout();
return redirect('/');
}
}
Key Features of the Custom Login:
- Custom Fields: We use
username
instead ofemail
. - Authentication Attempt: The
Auth::attempt()
method checks if the credentials match a user in the database. - Redirect: Upon successful login, the user is redirected to the intended page or a default route (e.g.,
dashboard
). - Validation: We validate the login request to ensure required fields are provided.
4. Customizing the Login View
Next, create a custom login form by editing or creating the resources/views/auth/login.blade.php
view file:
<form method="POST" action="{{ route('login') }}">
@csrf
<div>
<label for="username">Username</label>
<input id="username" type="text" name="username" required autofocus>
</div>
<div>
<label for="password">Password</label>
<input id="password" type="password" name="password" required>
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
In this form, we’ve replaced the email
field with a username
field. The view is simple but can be expanded with features like “Remember Me” or password reset links.
5. Customizing the User Model
To handle custom fields like username
for authentication, you will need to modify the User
model. Open the app/Models/User.php
file and ensure that the username
field is mass-assignable:
class User extends Authenticatable
{
protected $fillable = [
'username', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
}
If you’re using Laravel’s default Auth::attempt()
, it will automatically check against the password
field in your database. Ensure that your database migration includes a username
field, which can be done using:
Schema::table('users', function (Blueprint $table) {
$table->string('username')->unique();
});
6. Handling Login via Multiple Fields
Sometimes, you may want users to log in via either a username or an email. To handle this, modify the login logic in LoginController
:
public function login(Request $request)
{
$loginType = filter_var($request->input('login'), FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
$credentials = [
$loginType => $request->input('login'),
'password' => $request->input('password'),
];
if (Auth::attempt($credentials)) {
return redirect()->intended('dashboard');
}
return back()->withErrors([
'login' => 'The provided credentials do not match our records.',
]);
}
In this example, users can input either an email or a username into the login field, and the system will determine how to authenticate them.
7. Enhancing Security: Rate Limiting and Throttling
To prevent brute-force attacks, it’s important to add login throttling. Laravel includes a ThrottleRequests
middleware that can be applied to routes. To enable this, you can modify the LoginController
:
use Illuminate\Foundation\Auth\ThrottlesLogins;
class LoginController extends Controller
{
use ThrottlesLogins;
protected function login(Request $request)
{
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if (Auth::attempt($request->only('username', 'password'))) {
$this->clearLoginAttempts($request);
return redirect()->intended('dashboard');
}
$this->incrementLoginAttempts($request);
return back()->withErrors(['username' => 'Invalid credentials.']);
}
public function username()
{
return 'username'; // Specify the field used for login
}
}
This will automatically throttle login attempts and lock out users after too many failed attempts within a specific timeframe.
8. Adding Remember Me Functionality
To add “Remember Me” functionality, modify the login form and update the login logic:
In the Blade view (login.blade.php
):
<div>
<input type="checkbox" name="remember" id="remember">
<label for="remember">Remember Me</label>
</div>
In the LoginController
:
if (Auth::attempt($credentials, $request->filled('remember'))) {
return redirect()->intended('dashboard');
}
This enables persistent login sessions, even after users close their browsers.
9. Custom Redirects After Login
To control where users are redirected after login, you can modify the LoginController
by setting a custom redirect path:
protected function redirectTo()
{
return '/custom-dashboard';
}
Alternatively, you can define the redirectTo
path dynamically based on the user’s role or other conditions.
10. Logging and Event Listeners
For security auditing, you may want to log every login attempt or listen for successful logins and trigger additional actions. Laravel’s authentication system includes several events you can hook into, such as Login
, Logout
, and Failed
.
Example: Logging login events
Create an event listener:
php artisan make:listener LogSuccessfulLogin
Then, in LogSuccessfulLogin.php
:
public function handle(Login $event)
{
Log::info('User logged in: ', ['user' => $event->user->username]);
}
Register the listener in EventServiceProvider.php
under the $listen
array:
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LogSuccessfulLogin',
],
];
Conclusion
Laravel’s authentication system is powerful yet flexible, allowing you to create a customized login system that meets the specific needs of your application. From using custom fields like username
, adding security with throttling, handling multiple login methods, to creating event listeners, Laravel provides a robust framework that you can easily extend. By following these techniques, you can build a secure and user-friendly authentication system tailored to your project.