Laravel Login System: Complete Authentication Guide
Building a secure and user-friendly login system is the foundation of any modern web application. Laravel makes authentication incredibly straightforward with powerful built-in tools and starter kits. Whether you're creating a simple blog or a complex SaaS platform, mastering Laravel's authentication system is essential.
In this comprehensive guide, you'll learn everything about Laravel authentication—from choosing the right authentication method to implementing advanced security features like two-factor authentication. We'll compare Laravel Breeze, Jetstream, Fortify, and Sanctum, explore social login with OAuth, and share expert security best practices that keep your users' data safe.
⚡ Quick Navigation
Choose your learning path:
- Beginners: Start with Laravel Breeze for the simplest setup
- Intermediate: Jump to Custom Authentication for full control
- Advanced: Explore Two-Factor Authentication and API Authentication
Understanding Laravel Authentication in 2025
Laravel's authentication system is built on three core components: guards, providers, and middleware. Think of guards as security checkpoints that determine how users are authenticated for each request (typically session-based for web apps, token-based for APIs). Providers define how Laravel retrieves user data from your database or other storage.
Core Authentication Components
🛡️ Guards
Define HOW users authenticate (session, token, custom)
👥 Providers
Define WHERE user data comes from (database, Eloquent)
🚪 Middleware
Protect routes and control access to resources
All authentication configuration lives in config/auth.php, where you can customize guards, providers, and password reset behavior. Laravel's default setup uses session-based authentication with Eloquent ORM, which works perfectly for most applications.
Choosing Your Authentication Method
Laravel offers multiple authentication approaches, each suited for different project needs. Let's break down your options so you can make the right choice.
Authentication Methods Comparison
| Method | Best For | Complexity | Features |
|---|---|---|---|
| Laravel Breeze | Small to medium projects | ⭐ Simple | Login, registration, password reset |
| Laravel Jetstream | Enterprise applications | ⭐⭐⭐ Advanced | 2FA, teams, API tokens, profile management |
| Laravel Fortify | Headless/API-first apps | ⭐⭐ Moderate | Backend only, no views included |
| Laravel Sanctum | SPAs & mobile APIs | ⭐⭐ Moderate | API token authentication, SPA auth |
| Custom Authentication | Unique requirements | ⭐⭐⭐ Complex | Complete customization |
Quick Start with Laravel Breeze (Recommended for Beginners)
Laravel Breeze is the fastest way to add authentication to your Laravel application. It's lightweight, simple, and provides everything you need: login, registration, password reset, email verification, and password confirmation—all with clean, modern Blade templates you can easily customize.
Installation Steps
First, create a new Laravel project (or use an existing one):
# Create new Laravel project
composer create-project laravel/laravel my-auth-app
cd my-auth-app
# Configure your database in .env
# DB_DATABASE=your_database_name
# DB_USERNAME=your_username
# DB_PASSWORD=your_password
# Run migrations
php artisan migrateNow install Laravel Breeze:
# Install Breeze
composer require laravel/breeze --dev
# Install Breeze scaffolding (choose your stack)
php artisan breeze:install blade
# Or for React/Vue users:
# php artisan breeze:install react
# php artisan breeze:install vue
# Install and compile frontend dependencies
npm install
npm run dev
# Start the development server
php artisan serve✅ That's It!
Visit http://localhost:8000 and you'll see login and register links in the navigation. Breeze has scaffolded everything: routes, controllers, views, and even email verification functionality.
What Breeze Includes
- Login & Registration: Complete authentication forms with validation
- Password Reset: Secure email-based password recovery
- Email Verification: Optional email verification for new users
- Password Confirmation: Re-verify password for sensitive actions
- Profile Management: Basic profile update functionality
Laravel Jetstream for Advanced Features
Laravel Jetstream is Breeze's more powerful sibling. Built on Laravel Fortify, it includes everything Breeze offers plus advanced features perfect for production applications: two-factor authentication, API tokens via Sanctum, team management, and beautiful profile management pages.
Jetstream vs Breeze: Key Differences
🎯 When to Choose Breeze
- ✓ Simple authentication needs
- ✓ Quick project setup required
- ✓ Minimal dependencies preferred
- ✓ Learning Laravel basics
- ✓ Small to medium projects
🚀 When to Choose Jetstream
- ✓ Need two-factor authentication
- ✓ API token management required
- ✓ Team/organization features
- ✓ Professional profile management
- ✓ Enterprise-grade applications
Installing Laravel Jetstream
# Install Jetstream
composer require laravel/jetstream
# Install with Livewire (easier, reactive)
php artisan jetstream:install livewire
# Or with Inertia + Vue (SPA-like experience)
php artisan jetstream:install inertia
# Add team support (optional)
php artisan jetstream:install livewire --teams
# Install dependencies and compile assets
npm install
npm run dev
# Run migrations
php artisan migrate
# Start server
php artisan serve⚠️ Important Note
Jetstream is more opinionated than Breeze and comes with Tailwind CSS styling. While this means a beautiful interface out of the box, customization requires understanding Tailwind and either Livewire or Inertia.js.
Building Custom Authentication (Full Control)
Sometimes you need complete control over your authentication flow. Maybe you have unique business requirements, custom database schemas, or want to understand exactly how Laravel authentication works under the hood. Let's build a custom authentication system from scratch.
Step 1: Project Setup and Database
Start with a fresh Laravel installation:
composer create-project laravel/laravel custom-auth
cd custom-authConfigure your database in .env:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_auth
DB_USERNAME=root
DB_PASSWORD=Laravel's default user migration is already perfect for authentication. Run it:
php artisan migrateStep 2: Create Authentication Controllers
Generate the necessary controllers:
php artisan make:controller Auth/RegisterController
php artisan make:controller Auth/LoginController
php artisan make:controller Auth/DashboardControllerRegistration Controller
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
class RegisterController extends Controller
{
/**
* Show registration form
*/
public function showRegistrationForm()
{
return view('auth.register');
}
/**
* Handle registration request
*/
public function register(Request $request)
{
// Validate input
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
// Create user with hashed password
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
// Automatically log in the new user
Auth::login($user);
// Regenerate session to prevent fixation attacks
$request->session()->regenerate();
return redirect()->route('dashboard')
->with('success', 'Registration successful! Welcome aboard!');
}
}Login Controller
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
{
/**
* Show login form
*/
public function showLoginForm()
{
return view('auth.login');
}
/**
* Handle login request
*/
public function login(Request $request)
{
// Validate credentials
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required|string',
]);
// Rate limiting key (prevent brute force)
$throttleKey = strtolower($request->input('email')) . '|' . $request->ip();
// Check if too many login attempts
if (RateLimiter::tooManyAttempts($throttleKey, 5)) {
$seconds = RateLimiter::availableIn($throttleKey);
throw ValidationException::withMessages([
'email' => "Too many login attempts. Please try again in {$seconds} seconds.",
]);
}
// Attempt authentication
if (Auth::attempt($credentials, $request->boolean('remember'))) {
// Clear rate limiting on success
RateLimiter::clear($throttleKey);
// Regenerate session
$request->session()->regenerate();
return redirect()->intended('dashboard')
->with('success', 'Welcome back!');
}
// Record failed attempt
RateLimiter::hit($throttleKey, 60); // Lock for 60 seconds after 5 attempts
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
])->withInput($request->only('email'));
}
/**
* Handle logout request
*/
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('login')
->with('success', 'You have been logged out successfully.');
}
}Step 3: Define Routes
Add authentication routes in routes/web.php:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\DashboardController;
// Public routes
Route::get('/', function () {
return view('welcome');
})->name('home');
// Registration routes
Route::get('/register', [RegisterController::class, 'showRegistrationForm'])
->name('register')
->middleware('guest');
Route::post('/register', [RegisterController::class, 'register'])
->middleware('guest');
// Login routes
Route::get('/login', [LoginController::class, 'showLoginForm'])
->name('login')
->middleware('guest');
Route::post('/login', [LoginController::class, 'login'])
->middleware('guest');
// Logout route
Route::post('/logout', [LoginController::class, 'logout'])
->name('logout')
->middleware('auth');
// Protected routes (require authentication)
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])
->name('dashboard');
});Step 4: Create Blade Views
Create resources/views/auth/login.blade.php:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - {{ config('app.name') }}</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
<div class="max-w-md w-full bg-white rounded-lg shadow-lg p-8">
<h1 class="text-3xl font-bold text-center mb-6 text-gray-800">
Welcome Back
</h1>
@if(session('success'))
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
{{ session('success') }}
</div>
@endif
@if($errors->any())
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
<ul class="list-disc list-inside">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ route('login') }}" class="space-y-6">
@csrf
<div>
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
Email Address
</label>
<input
type="email"
name="email"
id="email"
value="{{ old('email') }}"
required
autofocus
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="[email protected]"
>
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">
Password
</label>
<input
type="password"
name="password"
id="password"
required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="••••••••"
>
</div>
<div class="flex items-center justify-between">
<label class="flex items-center">
<input
type="checkbox"
name="remember"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
>
<span class="ml-2 text-sm text-gray-600">Remember me</span>
</label>
<a href="#" class="text-sm text-blue-600 hover:text-blue-800">
Forgot password?
</a>
</div>
<button
type="submit"
class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition duration-200 font-semibold"
>
Sign In
</button>
</form>
<p class="mt-6 text-center text-sm text-gray-600">
Don't have an account?
<a href="{{ route('register') }}" class="text-blue-600 hover:text-blue-800 font-semibold">
Create one now
</a>
</p>
</div>
</body>
</html>💡 Pro Tip
Notice how we're using Tailwind CSS via CDN for quick styling. In production, install Tailwind properly with Laravel Mix or Vite for optimized performance and customization.
Implementing Social Login (OAuth)
Social login with Google, Facebook, GitHub, or Twitter dramatically improves user experience. Laravel Socialite makes OAuth authentication incredibly simple. Let's add Google login to your application.
Step 1: Install Socialite
composer require laravel/socialiteStep 2: Configure OAuth Credentials
First, create OAuth credentials in the Google Cloud Console. Then add them to your .env file:
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URL=http://localhost:8000/auth/google/callbackConfigure the service in config/services.php:
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URL'),
],
// Add more providers as needed
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => env('GITHUB_REDIRECT_URL'),
],
'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('FACEBOOK_REDIRECT_URL'),
],Step 3: Create Social Login Controller
php artisan make:controller Auth/SocialLoginController<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Str;
class SocialLoginController extends Controller
{
/**
* Redirect to OAuth provider
*/
public function redirectToProvider($provider)
{
// Validate provider
if (!in_array($provider, ['google', 'github', 'facebook'])) {
abort(404);
}
return Socialite::driver($provider)->redirect();
}
/**
* Handle OAuth callback
*/
public function handleProviderCallback($provider)
{
try {
// Get user from provider
$socialUser = Socialite::driver($provider)->user();
// Find or create user
$user = User::where('email', $socialUser->getEmail())->first();
if (!$user) {
// Create new user
$user = User::create([
'name' => $socialUser->getName(),
'email' => $socialUser->getEmail(),
'password' => Hash::make(Str::random(24)), // Random password
'email_verified_at' => now(), // Auto-verify social logins
'provider' => $provider,
'provider_id' => $socialUser->getId(),
'avatar' => $socialUser->getAvatar(),
]);
} else {
// Update existing user's provider info
$user->update([
'provider' => $provider,
'provider_id' => $socialUser->getId(),
'avatar' => $socialUser->getAvatar(),
]);
}
// Log in the user
Auth::login($user, true); // Remember user
return redirect()->route('dashboard')
->with('success', 'Successfully logged in with ' . ucfirst($provider));
} catch (\Exception $e) {
return redirect()->route('login')
->with('error', 'Unable to login with ' . ucfirst($provider) . '. Please try again.');
}
}
}Step 4: Update User Migration
Add OAuth fields to your users table:
php artisan make:migration add_oauth_fields_to_users_table --table=userspublic function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('provider')->nullable();
$table->string('provider_id')->nullable();
$table->string('avatar')->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['provider', 'provider_id', 'avatar']);
});
}php artisan migrateStep 5: Add Routes
use App\Http\Controllers\Auth\SocialLoginController;
// Social login routes
Route::get('/auth/{provider}/redirect', [SocialLoginController::class, 'redirectToProvider'])
->name('social.redirect');
Route::get('/auth/{provider}/callback', [SocialLoginController::class, 'handleProviderCallback'])
->name('social.callback');Step 6: Add Social Login Buttons
Add these buttons to your login form:
<div class="mt-6">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white text-gray-500">Or continue with</span>
</div>
</div>
<div class="mt-6 grid grid-cols-3 gap-3">
<!-- Google -->
<a href="{{ route('social.redirect', 'google') }}"
class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<svg class="w-5 h-5" viewBox="0 0 24 24">
<path fill="currentColor" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="currentColor" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="currentColor" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="currentColor" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
</a>
<!-- GitHub -->
<a href="{{ route('social.redirect', 'github') }}"
class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z" clip-rule="evenodd"/>
</svg>
</a>
<!-- Facebook -->
<a href="{{ route('social.redirect', 'facebook') }}"
class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M20 10c0-5.523-4.477-10-10-10S0 4.477 0 10c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V10h2.54V7.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V10h2.773l-.443 2.89h-2.33v6.988C16.343 19.128 20 14.991 20 10z" clip-rule="evenodd"/>
</svg>
</a>
</div>
</div>Adding Two-Factor Authentication (2FA)
Two-factor authentication adds a critical security layer by requiring users to verify their identity with a second factor—typically a time-based one-time password (TOTP) from apps like Google Authenticator or Authy. Laravel Fortify and Jetstream include 2FA by default, but you can also implement it manually.
Option 1: Using Laravel Jetstream (Easiest)
If you're using Jetstream, 2FA is already included! Users can enable it from their profile settings. Jetstream handles everything: QR code generation, recovery codes, and authentication challenges.
Option 2: Manual Implementation with Google2FA
For custom implementations, use the PragmaRX Google2FA package:
composer require pragmarx/google2fa-laravelAdd 2FA fields to your users table:
php artisan make:migration add_two_factor_to_users_table --table=userspublic function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('google2fa_secret')->nullable();
$table->boolean('google2fa_enabled')->default(false);
$table->timestamp('google2fa_enabled_at')->nullable();
});
}php artisan migrateCreating 2FA Controller
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Support\Facades\Auth;
class TwoFactorController extends Controller
{
protected $google2fa;
public function __construct()
{
$this->google2fa = new Google2FA();
}
/**
* Enable 2FA for user
*/
public function enable()
{
$user = Auth::user();
// Generate secret key
$secret = $this->google2fa->generateSecretKey();
// Store secret temporarily (not enabled yet)
$user->google2fa_secret = $secret;
$user->save();
// Generate QR code
$qrCodeUrl = $this->google2fa->getQRCodeUrl(
config('app.name'),
$user->email,
$secret
);
return view('auth.two-factor.setup', [
'qrCodeUrl' => $qrCodeUrl,
'secret' => $secret,
]);
}
/**
* Verify and activate 2FA
*/
public function verify(Request $request)
{
$request->validate([
'code' => 'required|string|size:6',
]);
$user = Auth::user();
$valid = $this->google2fa->verifyKey($user->google2fa_secret, $request->code);
if ($valid) {
$user->google2fa_enabled = true;
$user->google2fa_enabled_at = now();
$user->save();
return redirect()->route('dashboard')
->with('success', 'Two-factor authentication enabled successfully!');
}
return back()->withErrors(['code' => 'Invalid verification code.']);
}
/**
* Disable 2FA
*/
public function disable(Request $request)
{
$request->validate([
'password' => 'required|current_password',
]);
$user = Auth::user();
$user->google2fa_enabled = false;
$user->google2fa_secret = null;
$user->google2fa_enabled_at = null;
$user->save();
return redirect()->route('dashboard')
->with('success', 'Two-factor authentication disabled successfully!');
}
/**
* Show 2FA challenge during login
*/
public function showChallenge()
{
return view('auth.two-factor.challenge');
}
/**
* Verify 2FA code during login
*/
public function verifyChallenge(Request $request)
{
$request->validate([
'code' => 'required|string|size:6',
]);
$user = Auth::user();
$valid = $this->google2fa->verifyKey($user->google2fa_secret, $request->code);
if ($valid) {
session(['2fa_verified' => true]);
return redirect()->intended('dashboard');
}
return back()->withErrors(['code' => 'Invalid authentication code.']);
}
}🔐 Security Best Practice
Always provide users with recovery codes when enabling 2FA. These backup codes let users regain access if they lose their authenticator device. Generate and store 8-10 single-use recovery codes encrypted in your database.
API Authentication with Laravel Sanctum
Building a mobile app or single-page application? Laravel Sanctum provides lightweight authentication for APIs and SPAs using API tokens. It's simpler than OAuth (Laravel Passport) for first-party applications.
Installing Sanctum
# Sanctum comes pre-installed in Laravel 11+
# For older versions:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrateConfigure Sanctum
Add Sanctum middleware to app/Http/Kernel.php:
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],Update your User model to use Sanctum's HasApiTokens trait:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
// ...
}Creating API Tokens
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
/**
* Login and create API token
*/
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
'device_name' => 'required', // Track device for token management
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
// Create token with specific abilities/permissions
$token = $user->createToken($request->device_name, ['read', 'write'])->plainTextToken;
return response()->json([
'token' => $token,
'user' => $user,
]);
}
/**
* Logout and revoke token
*/
public function logout(Request $request)
{
// Revoke current token
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Logged out successfully']);
}
/**
* Logout from all devices
*/
public function logoutAll(Request $request)
{
// Revoke all tokens
$request->user()->tokens()->delete();
return response()->json(['message' => 'Logged out from all devices']);
}
}Protecting API Routes
In routes/api.php:
use App\Http\Controllers\Api\AuthController;
// Public routes
Route::post('/login', [AuthController::class, 'login']);
Route::post('/register', [RegisterController::class, 'register']);
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
Route::post('/logout', [AuthController::class, 'logout']);
Route::post('/logout-all', [AuthController::class, 'logoutAll']);
Route::get('/user', function (Request $request) {
return $request->user();
});
// Your other protected API routes
Route::apiResource('posts', PostController::class);
});Using API Tokens in Requests
# Login and get token
curl -X POST http://localhost:8000/api/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "password123",
"device_name": "iPhone 15"
}'
# Use token in subsequent requests
curl -X GET http://localhost:8000/api/user \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Accept: application/json"Security Best Practices for Laravel Authentication
Security isn't optional—it's essential. Here are the most important security practices for Laravel authentication systems:
1. Always Use HTTPS in Production
Force HTTPS to prevent man-in-the-middle attacks. Add this to your AppServiceProvider:
public function boot()
{
if ($this->app->environment('production')) {
\URL::forceScheme('https');
}
}2. Implement Rate Limiting (Prevent Brute Force)
Laravel includes rate limiting middleware. Apply it to login routes:
Route::post('/login', [LoginController::class, 'login'])
->middleware('throttle:5,1'); // 5 attempts per minute
// Or customize in RouteServiceProvider:
RateLimiter::for('login', function (Request $request) {
$email = (string) $request->email;
return Limit::perMinute(5)->by($email . $request->ip());
});3. Use Strong Password Requirements
use Illuminate\Validation\Rules\Password;
$request->validate([
'password' => [
'required',
'confirmed',
Password::min(8)
->mixedCase()
->numbers()
->symbols()
->uncompromised(), // Check against data breaches
],
]);4. Implement CSRF Protection
Laravel includes CSRF protection by default. Always include the @csrf directive in forms:
<form method="POST" action="/login">
@csrf
<!-- form fields -->
</form>5. Secure Session Configuration
Configure sessions securely in config/session.php:
'secure' => env('SESSION_SECURE_COOKIE', true), // HTTPS only
'http_only' => true, // Prevent XSS attacks
'same_site' => 'lax', // CSRF protection
'lifetime' => 120, // 2 hours6. Hash Passwords Properly
Always use Laravel's Hash facade with bcrypt:
use Illuminate\Support\Facades\Hash;
// Creating user
$user->password = Hash::make($request->password);
// Verifying password
if (Hash::check($plainPassword, $hashedPassword)) {
// Password is correct
}
// NEVER store plain-text passwords!
// NEVER use md5() or sha1() for passwords!7. Regenerate Session After Login
// Prevent session fixation attacks
$request->session()->regenerate();8. Implement Email Verification
Laravel makes email verification easy:
// User model
use Illuminate\Contracts\Auth\MustVerifyEmail;
class User extends Authenticatable implements MustVerifyEmail
{
// ...
}
// Routes
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});9. Log Authentication Events
// In EventServiceProvider
protected $listen = [
\Illuminate\Auth\Events\Login::class => [
\App\Listeners\LogSuccessfulLogin::class,
],
\Illuminate\Auth\Events\Failed::class => [
\App\Listeners\LogFailedLogin::class,
],
\Illuminate\Auth\Events\Logout::class => [
\App\Listeners\LogSuccessfulLogout::class,
],
];10. Keep Laravel Updated
Regularly update Laravel and dependencies for security patches:
composer update
php artisan view:clear
php artisan config:clear
php artisan cache:clear⚠️ Common Security Mistakes to Avoid
- Storing passwords in plain text
- Not using HTTPS in production
- Forgetting to implement rate limiting
- Exposing sensitive data in error messages
- Not validating and sanitizing user input
- Using weak password requirements
Troubleshooting Common Authentication Issues
Issue 1: "These credentials do not match our records"
Possible Causes:
- Password not hashed in database (use
Hash::make()) - Wrong column names in login attempt
- User doesn't exist
- Database connection issues
Solution:
// Check if password is hashed
$user = User::first();
dd($user->password); // Should start with $2y$
// Verify column names match
Auth::attempt([
'email' => $request->email, // Must match database column
'password' => $request->password
]);Issue 2: CSRF Token Mismatch
Solution:
// 1. Always include @csrf in forms
<form method="POST">
@csrf
<!-- ... -->
</form>
// 2. Check session configuration
// config/session.php - ensure 'same_site' is set properly
// 3. Clear cache
php artisan config:clear
php artisan cache:clearIssue 3: Redirect Loop After Login
Solution:
// Check middleware on dashboard route
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware('auth'); // Not 'guest'
// Verify redirectTo is correct
protected $redirectTo = '/dashboard'; // Not '/login'Issue 4: Session Not Persisting
Solution:
# 1. Check storage permissions
chmod -R 775 storage
chmod -R 775 bootstrap/cache
# 2. Verify .env session configuration
SESSION_DRIVER=file
SESSION_LIFETIME=120
# 3. Clear sessions
php artisan session:clear
php artisan cache:clearIssue 5: 419 Page Expired Error
Solution:
// In app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
// Add routes to exempt (use sparingly!)
'api/*',
];
// Better: Ensure forms include @csrf and AJAX includes X-CSRF-TOKENWrapping Up: Your Path to Secure Laravel Authentication
You've now mastered Laravel's authentication system! From quick setups with Breeze to advanced features like 2FA and API authentication with Sanctum, you have all the tools to build secure, professional login systems.
Quick Recap
✅ You've Learned:
- How Laravel authentication works (guards, providers, middleware)
- Choosing between Breeze, Jetstream, Fortify, and custom auth
- Implementing social login with OAuth
- Adding two-factor authentication
- API authentication with Sanctum
- Critical security best practices
- Troubleshooting common issues
🚀 Next Steps:
- Implement email verification
- Add remember me functionality
- Create password strength indicators
- Build user profile management
- Add activity logging
- Implement account recovery
- Explore Laravel Permissions packages (Spatie)
Remember: security is an ongoing process, not a one-time setup. Stay updated with Laravel releases, follow security advisories, and always validate user input. Your users trust you with their data—handle it responsibly.
💬 Need Help?
Join the Laravel community:
Frequently Asked Questions
Related Articles You May Like
- Top Coding Tips: Clean Code, Boost Productivity, Master Practices
Best Practices • Intermediate
- The Ultimate Guide to Code Debugging: Techniques, Tools & Tips
Debugging • Intermediate
- GitHub Actions: Complete CI/CD Automation Guide
Git • Intermediate
- Laravel API Example: Creating Efficient Endpoints
Laravel • Advanced
- Laravel Tips and Tricks: Hidden Features Most Developers Miss
Laravel • Advanced
- How to Debug Laravel SQL Queries in API Requests: A Developer's Guide
Laravel • Intermediate