Building Custom Authentication in Laravel: A Deep Dive into Blade-Based Login Systems
Overview
Most developers reach for
Prerequisites
To follow this tutorial, you should have
Key Libraries & Tools
- Laravel Framework: The core PHPframework providing the auth facades and routing engine.
- Blade Templating: Laravel's native templating engine for creating dynamic HTML forms.
- SQLite: A lightweight, file-based database used for quick development and testing.
- PHPStorm: The IDE used for writing and managing the codebase during this walkthrough.
Code Walkthrough
1. Defining the Routes
Everything starts in routes/web.php. You must define routes for displaying the forms and handling the post requests. Unlike starter kits, we explicitly name our routes to keep our
Route::get('/login', function () {
return view('login');
})->name('login');
Route::post('/login', LoginController::class)->name('login.attempt');
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard')->middleware('auth');
2. The Login Controller
Auth::attempt method. This method automatically handles password hashing comparisons and session creation. Note the use of request()->regenerate() to prevent session fixation attacks.
public function __invoke(Request $request)
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
return redirect()->intended('dashboard');
}
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
]);
}
3. Registering New Users
For registration, you manually hash the password before saving it to the database. bcrypt helper ensures the password isn't stored in plain text. After creating the user, use Auth::login($user) to immediately authenticate the new account.
public function store(Request $request)
{
$userData = $request->validate([
'name' => 'required|string',
'email' => 'required|email|unique:users',
'password' => 'required|min:8',
]);
$userData['password'] = bcrypt($userData['password']);
$user = User::create($userData);
Auth::login($user);
return redirect()->route('dashboard');
}
Syntax Notes
- Single Action Controllers: Using the
__invokemethod allows a controller to handle exactly one route, making your logic modular and easy to find. - Blade Directives: The
@csrfdirective is non-negotiable for anyPOSTrequest inLaravel. It generates a hidden token field that protects your application against cross-site request forgery. - Validation Arrays: Passing an array of rules to
$request->validate()is the standard way to ensure data integrity before it touches your database.
Practical Examples
This custom approach is ideal for specialized applications. For instance, if you are building an internal company tool that requires login via a unique Username instead of an email, you can simply swap the validation key in the controller and the input type in the
Tips & Gotchas
- The Session Trap: Always remember to call
session()->invalidate()andsession()->regenerateToken()during the logout process. If you don't, you leave the user's session vulnerable to hijacking. - Rate Limiting: Use the
throttlemiddleware on your login routes. Without it, your app is an open target for brute-force attacks. A simplemiddleware('throttle:5,1')limits users to five attempts per minute. - Fillable Property: If you add new fields like
usernameto your database, you must update the$fillablearray in yourUsermodel. Otherwise,Laravel's mass-assignment protection will silently discard the data.
