Laravel Authentication: It's Already Built

SS Saurav Sitaula

After weeks of building login systems from scratch, I discovered Laravel's built-in authentication. One command, and I had registration, login, password reset, and middleware protection. The day I stopped reinventing the wheel.

The Homemade Login System

After setting up Laravel basics, I needed user authentication. Registration, login, password reset — the usual.

I started building it from scratch:

// My attempt at a login controller
public function login(Request $request)
{
    $user = User::where('email', $request->email)->first();
    
    if ($user && password_verify($request->password, $user->password)) {
        session(['user_id' => $user->id]);
        return redirect('/dashboard');
    }
    
    return back()->with('error', 'Invalid credentials');
}

It worked, kind of. But I had to build:

  • Registration form and controller
  • Login form and controller
  • Logout logic
  • Session management
  • Password hashing
  • Password reset flow
  • Email verification
  • “Remember me” functionality
  • CSRF protection
  • Rate limiting for login attempts

That’s a lot of security-critical code. One mistake and users get hacked.

Then a colleague asked: “Why aren’t you using Laravel’s auth?”

The Command That Changed Everything

php artisan make:auth

That’s it. One command.

Laravel generated:

  • Login page (/login)
  • Registration page (/register)
  • Password reset flow (/password/reset)
  • Home page after login (/home)
  • All the controllers
  • All the views
  • All the routes

I refreshed my browser. There was a fully functional login system. Registration worked. Password reset sent emails. Everything just… worked.

I had spent two days building what Laravel gave me in 5 seconds.

What Laravel Auth Includes

Routes

In routes/web.php, the Auth::routes() call registers:

// Authentication Routes
POST   /login LoginController@login
POST   /logout LoginController@logout

// Registration Routes  
GET    /register RegisterController@showRegistrationForm
POST   /register RegisterController@register

// Password Reset Routes
GET    /password/reset ForgotPasswordController@showLinkRequestForm
POST   /password/email ForgotPasswordController@sendResetLinkEmail
GET    /password/reset/{token}  ResetPasswordController@showResetForm
POST   /password/reset ResetPasswordController@reset

All mapped to pre-built controllers that handle edge cases I’d never think of.

Controllers

The generated controllers in app/Http/Controllers/Auth/:

// LoginController.php
class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo = '/home';

    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
}

See that use AuthenticatesUsers? That trait contains hundreds of lines of battle-tested authentication logic. Rate limiting, lockouts, remember tokens, everything.

Views

Blade templates in resources/views/auth/:

  • login.blade.php
  • register.blade.php
  • passwords/email.blade.php
  • passwords/reset.blade.php

All styled with Bootstrap by default. I customized them to match my design.

Middleware: Protecting Routes

The real power is middleware. Want to protect a route so only logged-in users can access it?

// routes/web.php
Route::get('/dashboard', 'DashboardController@index')->middleware('auth');

// Or protect a group of routes
Route::middleware('auth')->group(function () {
    Route::get('/dashboard', 'DashboardController@index');
    Route::get('/profile', 'ProfileController@show');
    Route::get('/settings', 'SettingsController@index');
});

Try to access /dashboard without logging in? Redirected to /login automatically.

The Auth Middleware

Laravel includes the auth middleware out of the box. It checks if the user is authenticated:

// Simplified version of what it does
public function handle($request, Closure $next)
{
    if (!Auth::check()) {
        return redirect('/login');
    }
    
    return $next($request);
}

Creating Custom Middleware

I needed to check if users were admins:

php artisan make:middleware CheckAdmin
// app/Http/Middleware/CheckAdmin.php
<?php

namespace App\Http\Middleware;

use Closure;

class CheckAdmin
{
    public function handle($request, Closure $next)
    {
        if (!auth()->user()->is_admin) {
            abort(403, 'Unauthorized');
        }
        
        return $next($request);
    }
}

Register it in app/Http/Kernel.php:

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'admin' => \App\Http\Middleware\CheckAdmin::class,  // Add this
    // ...
];

Use it:

Route::middleware(['auth', 'admin'])->group(function () {
    Route::get('/admin/users', 'Admin\UserController@index');
    Route::delete('/admin/users/{id}', 'Admin\UserController@destroy');
});

Only authenticated admins can access these routes. Everyone else gets a 403.

Accessing the Authenticated User

In controllers:

public function dashboard()
{
    $user = auth()->user();  // The logged-in user
    
    return view('dashboard', [
        'user' => $user,
        'posts' => $user->posts  // Assuming relationship exists
    ]);
}

In Blade templates:

@auth
    <p>Welcome, {{ auth()->user()->name }}!</p>
    <a href="/logout">Logout</a>
@else
    <a href="/login">Login</a>
    <a href="/register">Register</a>
@endauth

The @auth and @guest directives make conditional rendering clean.

Customizing the Login Logic

Need to check if users are “active” before allowing login?

// app/Http/Controllers/Auth/LoginController.php
class LoginController extends Controller
{
    use AuthenticatesUsers;

    // Add custom validation
    protected function credentials(Request $request)
    {
        return array_merge(
            $request->only($this->username(), 'password'),
            ['active' => true]  // User must be active
        );
    }
    
    // Custom response after login
    protected function authenticated(Request $request, $user)
    {
        if ($user->is_admin) {
            return redirect('/admin/dashboard');
        }
        
        return redirect('/dashboard');
    }
}

Override the methods you need, leave the rest to Laravel.

Password Reset Flow

The password reset flow that would take days to build manually:

  1. User clicks “Forgot Password”
  2. Enters email, submits form
  3. Laravel sends email with unique token
  4. User clicks link in email
  5. User enters new password
  6. Token is verified and password is updated
  7. User is logged in automatically

All handled by:

// ForgotPasswordController uses this trait
use SendsPasswordResetEmails;

// ResetPasswordController uses this trait  
use ResetsPasswords;

I just had to configure email settings in .env:

MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password

Remember Me Functionality

That “Remember Me” checkbox on login forms? Laravel handles it:

<div class="form-check">
    <input type="checkbox" name="remember" id="remember">
    <label for="remember">Remember Me</label>
</div>

The AuthenticatesUsers trait automatically checks for this field and creates a long-lived “remember token” stored in the database. Users stay logged in even after closing the browser.

Session Configuration

Laravel stores sessions in files by default, but you can change it:

SESSION_DRIVER=database

Run the migration:

php artisan session:table
php artisan migrate

Now sessions are in the database. Good for multiple servers.

Other options: redis, memcached, cookie, array (for testing).

Guards: Multiple Authentication Systems

My app had regular users AND admin users with separate tables. Laravel guards handle this:

// config/auth.php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'admin' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],
    'admins' => [
        'driver' => 'eloquent',
        'model' => App\Admin::class,
    ],
],

Use different guards:

// Regular user login
Auth::guard('web')->attempt($credentials);

// Admin login
Auth::guard('admin')->attempt($credentials);

// Check in middleware
Route::middleware('auth:admin')->group(function () {
    // Only admins
});

CSRF Protection

Every form in Laravel needs a CSRF token:

<form method="POST" action="/profile">
    @csrf
    <input name="name" value="{{ $user->name }}">
    <button type="submit">Update</button>
</form>

The @csrf directive adds a hidden input with a token. Laravel verifies it on submission. No more Cross-Site Request Forgery attacks.

Forget the @csrf? You get a 419 error. Laravel protects you from yourself.

What I Wish I’d Known Earlier

  1. make:auth is your starting point. Customize from there, don’t build from scratch.

  2. Read the traits. AuthenticatesUsers, RegistersUsers — reading these teaches you how authentication actually works.

  3. Middleware can be stacked. ->middleware(['auth', 'verified', 'admin']) runs all three in order.

  4. Use auth() helper everywhere. It’s cleaner than Auth::user() and works in Blade, controllers, and models.

  5. Test the password reset flow. Set up Mailtrap early so you can actually test email functionality.

The Journey Continues

Authentication was handled. Routes were protected. But my app needed to do more than just guard pages.

I needed to send welcome emails. Notify users about events. Even send SMS for critical alerts.

Laravel had built-in systems for all of that too.


P.S. — If you’ve built authentication from scratch before, you know the anxiety. “Did I hash the password correctly? Is my session secure? What about timing attacks?” Laravel’s auth is maintained by security experts and battle-tested by millions of apps. Use it. Sleep better.

SS

Saurav Sitaula

Software Architect • Nepal

Back to all posts
Saurav.dev

© 2026 Saurav Sitaula.AstroNeoBrutalism