Introduction to Authentication
Authentication is the process of verifying the identity of users in your application. It's a fundamental requirement for most web applications, allowing users to securely access their personal data and enabling you to restrict access to certain parts of your application.
Laravel provides a complete, secure authentication system right out of the box, saving you from having to implement these critical security features yourself. The system includes:
- User registration and login
- Password reset functionality
- Remember me capabilities
- Email verification
- Protection against common security vulnerabilities
Think of authentication as the security checkpoint at an airport or building. It verifies that people are who they claim to be before granting access to restricted areas, just as authentication verifies users before granting them access to protected routes and resources in your application.
Laravel's Authentication System
Laravel provides several authentication systems with slightly different approaches:
Laravel Breeze
A lightweight implementation of Laravel's authentication features including login, registration, password reset, email verification, and password confirmation. It includes simple Blade templates styled with Tailwind CSS.
Best for: Simple applications or starter projects where you need a minimal, straightforward authentication system that you can modify.
Laravel Jetstream
A more robust starter kit that includes authentication and provides additional features like two-factor authentication, session management, API support with Laravel Sanctum, team management, and more. Jetstream offers the choice of Livewire or Inertia.js for the frontend.
Best for: Applications that need advanced authentication features and modern frontend frameworks.
Laravel Fortify
A headless authentication backend implementation for Laravel. It provides the backend functionality for authentication without any frontend views, allowing you to pair it with any frontend you choose.
Best for: Applications where you want full control over the authentication frontend or are using a separate frontend framework/SPA.
Laravel UI
The original Laravel authentication scaffolding package that provides Bootstrap, Vue, or React views for authentication features. While it's still maintained, newer projects typically use one of the options above.
Best for: Applications that need to use Bootstrap or when working with legacy Laravel codebases.
These authentication systems are like different types of security systems for buildings - from basic key card access to sophisticated biometric systems. They all achieve the same core purpose but offer different levels of features and complexity.
Setting Up Authentication
Let's explore how to set up authentication in a Laravel application using Breeze, which provides a simple implementation that's easy to understand and extend:
Installing Laravel Breeze
// Install Laravel Breeze via Composer
composer require laravel/breeze --dev
// Install Breeze with Blade views
php artisan breeze:install blade
// Install Breeze with React
php artisan breeze:install react
// Install Breeze with Vue
php artisan breeze:install vue
// Install Breeze with API-only endpoints
php artisan breeze:install api
// After installation, migrate the database to create users table
php artisan migrate
// For frontend scaffolding (Blade, React, Vue), compile assets
npm install
npm run dev
When you install Breeze, Laravel creates several components for you:
- Controllers for authentication (login, registration, password reset, etc.)
- Views for authentication forms (with Blade option)
- Routes for authentication endpoints
- Middleware for protecting routes
- Email verification functionality
After installation, you'll have a complete authentication system ready to use, with endpoints for login, registration, password reset, and email verification.
If you want to use the built-in authentication but with your own styling or structure, you can publish the Breeze views and modify them:
// Publish Breeze views for customization
php artisan vendor:publish --tag=laravel-breeze-views
The User Model and Authentication
At the heart of Laravel's authentication system is the User model. By default, this model implements the Authenticatable interface and uses several traits to enable authentication features:
Default User Model
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
Key components of the User model:
- Authenticatable Class: Provides core authentication functionality
- MustVerifyEmail Interface: Adds email verification capabilities
- HasApiTokens Trait: Enables API token authentication via Sanctum
- Notifiable Trait: Allows sending notifications (like password reset emails)
- $fillable Array: Defines mass-assignable attributes for registration
- $hidden Array: Ensures sensitive data isn't included in model arrays/JSON
- $casts Array: Automatically handles data type conversions
The 'password' attribute is automatically hashed when set, thanks to Laravel's built-in mutator:
// When you do this:
$user = new User;
$user->password = 'plain-text-password';
$user->save();
// Laravel automatically hashes it before saving to the database
// You never need to manually hash passwords
The User model in Laravel's authentication system is like a secure digital identity card. It contains all the necessary information about a user, handles the secure storage of sensitive data like passwords, and provides methods for verifying the user's identity.
Using Authentication in Controllers
Laravel provides several ways to work with authentication in your controllers:
Authentication in Controllers
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class DashboardController extends Controller
{
/**
* Create a new controller instance.
*/
public function __construct()
{
// Protect all controller methods with authentication
$this->middleware('auth');
// Or apply selectively
// $this->middleware('auth')->only(['sensitive', 'methods']);
// $this->middleware('auth')->except(['public', 'methods']);
}
/**
* Show the dashboard.
*/
public function index(Request $request)
{
// Get the authenticated user
$user = Auth::user();
// or
$user = $request->user();
// or
$user = auth()->user();
// Check if user is authenticated
if (Auth::check()) {
// User is logged in
}
// Get user ID
$userId = Auth::id();
return view('dashboard', [
'user' => $user
]);
}
/**
* Manual authentication
*/
public function authenticate(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.',
])->onlyInput('email');
}
/**
* Logout
*/
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
Password Confirmation
For sensitive operations, you might want to reconfirm the user's password:
// In a controller method
if (! Hash::check($request->password, $request->user()->password)) {
return back()->withErrors([
'password' => ['The provided password does not match our records.']
]);
}
// Or using the built-in middleware
public function __construct()
{
$this->middleware('password.confirm')->only('updatePaymentMethod');
}
Using authentication in controllers is like having security personnel verify identification before allowing access to restricted areas. The middleware acts as the initial checkpoint, while the Auth facade gives you tools to check credentials and identify users throughout your application.
Authentication Guards & Providers
Laravel's authentication system is built around the concept of "guards" and "providers". Understanding these components helps you customize authentication for complex applications:
Guards
Guards determine how users are authenticated for each request. They define the method of authentication (session, token, etc.):
// In config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'sanctum', // Or 'token', 'passport'
'provider' => 'users',
],
],
Providers
Providers define how users are retrieved from your persistent storage (database):
// In config/auth.php
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// Example of a database provider (without Eloquent)
'admins' => [
'driver' => 'database',
'table' => 'admins',
],
],
Using Different Guards
You can specify which guard to use in various authentication operations:
// Specify guard when checking authentication
if (Auth::guard('admin')->check()) {
// User is authenticated with admin guard
}
// Specify guard in middleware
$this->middleware('auth:admin');
// Manually authenticate with specific guard
if (Auth::guard('admin')->attempt($credentials)) {
// Authentication successful
}
// Get user from specific guard
$user = Auth::guard('admin')->user();
Multiple Authentication Systems
You can implement multiple authentication systems for different user types:
// Create a separate Admin model
class Admin extends Authenticatable
{
// Admin-specific configuration
}
// Configure in auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
],
Guards and providers in Laravel authentication are like having different security systems for different parts of a building. Guards are the security checkpoints (using different methods like ID cards, fingerprints, or facial recognition), while providers are the databases of authorized personnel that the guards consult to verify identities.
Protecting Routes
Laravel provides several ways to protect routes, ensuring that only authenticated users can access certain parts of your application:
Route Middleware
// In routes/web.php
// Single protected route
Route::get('/dashboard', function () {
// Only authenticated users can access this route
})->middleware('auth');
// Group of protected routes
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::get('/profile', [ProfileController::class, 'show']);
Route::put('/profile', [ProfileController::class, 'update']);
});
// With auth guard specified
Route::middleware(['auth:admin'])->group(function () {
Route::get('/admin/dashboard', [AdminController::class, 'index']);
});
// Combined middleware
Route::middleware(['auth', 'verified'])->group(function () {
// Only authenticated users with verified emails
});
Controller Middleware
class DashboardController extends Controller
{
public function __construct()
{
// Apply to all methods
$this->middleware('auth');
// Apply to specific methods
$this->middleware('verified')->only('sensitiveOperation');
// Exclude from specific methods
$this->middleware('auth')->except('preview');
}
}
Middleware Aliases
// In app/Http/Kernel.php
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
Laravel's authentication middleware is like having security guards stationed at different entrances to a building. They check if visitors have the proper credentials before allowing them to enter protected areas. The middleware also handles redirecting unauthorized users to the login page automatically.
Email Verification
Laravel includes built-in support for email verification, ensuring that users have access to the email accounts they registered with:
Setting Up Email Verification
// 1. Implement MustVerifyEmail on the User model
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements MustVerifyEmail
{
// User model implementation
}
// 2. Use the 'verified' middleware to protect routes
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
// 3. Ensure your application has the verification routes
// Typically included with auth scaffolding
Route::get('/email/verify', function () {
return view('auth.verify-email');
})->middleware('auth')->name('verification.notice');
Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
$request->fulfill();
return redirect('/home');
})->middleware(['auth', 'signed'])->name('verification.verify');
Route::post('/email/verification-notification', function (Request $request) {
$request->user()->sendEmailVerificationNotification();
return back()->with('message', 'Verification link sent!');
})->middleware(['auth', 'throttle:6,1'])->name('verification.send');
Customizing Verification Emails
// Create a custom notification
php artisan make:notification CustomVerifyEmail
// In the notification class
use Illuminate\Auth\Notifications\VerifyEmail;
class CustomVerifyEmail extends VerifyEmail
{
protected function buildMailMessage($url)
{
return (new MailMessage)
->subject('Verify Your Email Address')
->line('Please click the button below to verify your email address.')
->action('Verify Email Address', $url)
->line('If you did not create an account, no further action is required.');
}
}
// In the User model
protected function sendEmailVerificationNotification()
{
$this->notify(new \App\Notifications\CustomVerifyEmail);
}
Email verification in Laravel is like having a two-step registration process for a secure facility. First, users register with their credentials, then they must prove they have access to their claimed email address by clicking a secure link sent to that address, confirming their identity before gaining full access.
Password Reset
Laravel includes a secure password reset system that enables users to recover access to their accounts through email:
Password Reset Flow
// The password reset routes are typically included with auth scaffolding
// 1. User requests a password reset link
Route::get('/forgot-password', [PasswordResetLinkController::class, 'create'])
->middleware('guest')
->name('password.request');
Route::post('/forgot-password', [PasswordResetLinkController::class, 'store'])
->middleware('guest')
->name('password.email');
// 2. User clicks link in email and sets new password
Route::get('/reset-password/{token}', [NewPasswordController::class, 'create'])
->middleware('guest')
->name('password.reset');
Route::post('/reset-password', [NewPasswordController::class, 'store'])
->middleware('guest')
->name('password.update');
Customizing Password Reset Emails
// Create a custom notification
php artisan make:notification CustomResetPasswordNotification
// In the notification class
use Illuminate\Auth\Notifications\ResetPassword;
class CustomResetPasswordNotification extends ResetPassword
{
protected function buildMailMessage($url)
{
return (new MailMessage)
->subject('Reset Your Password')
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', $url)
->line('This password reset link will expire in ' . config('auth.passwords.users.expire') . ' minutes.')
->line('If you did not request a password reset, no further action is required.');
}
}
// In the User model
protected function sendPasswordResetNotification($token)
{
$this->notify(new \App\Notifications\CustomResetPasswordNotification($token));
}
The password reset system in Laravel is like having a secure identity recovery process. When users forget their "key" (password), the system verifies their identity through another trusted channel (email) before allowing them to create a new key.
Authorization with Gates and Policies
While authentication verifies who users are, authorization determines what they can do. Laravel provides a powerful system for authorization through Gates and Policies:
Gates
Gates are simple closures that determine if a user can perform a given action:
// In app/Providers/AuthServiceProvider.php
public function boot()
{
// Define gates
Gate::define('edit-post', function (User $user, Post $post) {
return $user->id === $post->user_id || $user->isAdmin();
});
Gate::define('publish-post', function (User $user) {
return $user->isEditor() || $user->isAdmin();
});
// Define a gate using a class and method
Gate::define('moderate-comments', [CommentPolicy::class, 'moderate']);
}
// Using gates in controllers
public function edit(Post $post)
{
if (Gate::allows('edit-post', $post)) {
// User can edit the post
}
if (Gate::denies('edit-post', $post)) {
// User cannot edit the post
}
// Authorize or abort with 403
Gate::authorize('edit-post', $post);
}
// Using gates in blade templates
@can('edit-post', $post)
<a href="{{ route('posts.edit', $post) }}">Edit Post</a>
@endcan
@cannot('publish-post')
<p>Only editors can publish posts.</p>
@endcannot
Policies
Policies organize authorization logic around a model or resource:
Creating and Registering Policies
// Generate a policy
php artisan make:policy PostPolicy --model=Post
// Register in AuthServiceProvider
protected $policies = [
Post::class => PostPolicy::class,
];
// The policy will be auto-discovered by convention
// Post model -> PostPolicy
Policy Implementation
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
/**
* Determine if the given post can be viewed by the user.
*/
public function view(User $user, Post $post)
{
// All users can view public posts
if (!$post->is_private) {
return true;
}
// Users can view their own private posts
return $user->id === $post->user_id;
}
/**
* Determine if the user can create posts.
*/
public function create(User $user)
{
// All authenticated users can create posts
return true;
}
/**
* Determine if the user can update the post.
*/
public function update(User $user, Post $post)
{
// Users can update their own posts
return $user->id === $post->user_id;
}
/**
* Determine if the user can delete the post.
*/
public function delete(User $user, Post $post)
{
// Admins can delete any post
if ($user->isAdmin()) {
return true;
}
// Users can delete their own posts
return $user->id === $post->user_id;
}
/**
* Policy for publishing posts
*/
public function publish(User $user, Post $post)
{
// Only editors and admins can publish
if (!($user->isEditor() || $user->isAdmin())) {
return false;
}
// Only published posts in your own department
return $user->department_id === $post->department_id;
}
/**
* Policy filter - run before all other checks
*/
public function before(User $user, $ability)
{
// Superadmins can do anything
if ($user->isSuperAdmin()) {
return true;
}
// Null means continue to the specific policy method
return null;
}
}
Using Policies
// In controllers
public function update(Request $request, Post $post)
{
// Automatically uses the registered policy
$this->authorize('update', $post);
// Update logic...
}
public function store(Request $request)
{
// For methods that don't require a model instance
$this->authorize('create', Post::class);
// Create logic...
}
// Using policy with user
if ($user->can('update', $post)) {
// Can update
}
if ($user->cannot('delete', $post)) {
// Cannot delete
}
// In Blade templates
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Edit</a>
@endcan
@cannot('publish', $post)
<p>You don't have permission to publish.</p>
@endcannot
Gates and policies in Laravel are like having a rule book for what different users can do within your application. Gates are simple, specific rules ("only managers can approve expense reports"), while policies are comprehensive rule collections for specific types of resources ("here are all the rules about who can view, edit, and delete documents").
Role-Based Authorization
While Laravel doesn't include a role-based authorization system out of the box, you can easily implement one:
Simple Role Implementation
// Add a 'role' column to users table
Schema::table('users', function (Blueprint $table) {
$table->string('role')->default('user');
});
// In User model
public function isAdmin()
{
return $this->role === 'admin';
}
public function isEditor()
{
return $this->role === 'editor';
}
// In AuthServiceProvider
Gate::define('manage-users', function (User $user) {
return $user->isAdmin();
});
Many-to-Many Relationship for Roles
// Create roles and permissions tables
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('label')->nullable();
$table->timestamps();
});
Schema::create('permissions', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('label')->nullable();
$table->timestamps();
});
Schema::create('permission_role', function (Blueprint $table) {
$table->primary(['permission_id', 'role_id']);
$table->foreignId('permission_id')->constrained()->onDelete('cascade');
$table->foreignId('role_id')->constrained()->onDelete('cascade');
});
Schema::create('role_user', function (Blueprint $table) {
$table->primary(['role_id', 'user_id']);
$table->foreignId('role_id')->constrained()->onDelete('cascade');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
});
// In User model
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function hasRole($role)
{
if (is_string($role)) {
return $this->roles->contains('name', $role);
}
// Check if user has any of the given roles
if (is_array($role)) {
foreach ($role as $r) {
if ($this->hasRole($r)) {
return true;
}
}
return false;
}
// If $role is a Role model or collection
return $role->intersect($this->roles)->isNotEmpty();
}
public function hasPermission($permission)
{
return $this->roles->flatMap(function ($role) {
return $role->permissions;
})->contains('name', $permission);
}
Using Roles for Authorization
// In AuthServiceProvider
Gate::define('edit-posts', function (User $user) {
return $user->hasRole(['editor', 'admin']);
});
Gate::define('approve-comments', function (User $user) {
return $user->hasPermission('moderate-comments');
});
// Custom middleware for roles
class CheckRole
{
public function handle($request, $next, $role)
{
if (!$request->user() || !$request->user()->hasRole($role)) {
abort(403, 'Unauthorized action.');
}
return $next($request);
}
}
// Register in Kernel.php
protected $middlewareAliases = [
// ...
'role' => \App\Http\Middleware\CheckRole::class,
];
// Use in routes
Route::middleware(['auth', 'role:admin'])->group(function () {
Route::get('/admin/dashboard', [AdminController::class, 'index']);
});
For more complex role-based authorization, you might want to use a package like Spatie's Laravel Permission, which provides a complete, well-tested implementation:
Using Spatie's Laravel Permission
// Install via Composer
composer require spatie/laravel-permission
// Publish and run migrations
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
// In User model
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
// ...
}
// Usage
// Assign roles and permissions
$user->assignRole('writer');
$user->givePermissionTo('edit articles');
// Check roles and permissions
$user->hasRole('writer');
$user->hasPermissionTo('edit articles');
$user->hasAnyRole(['writer', 'admin']);
// In middleware
Route::middleware(['role:admin|writer'])->group(function () {
// Routes for admins and writers
});
Route::middleware(['permission:publish articles'])->group(function () {
// Routes for users who can publish articles
});
Role-based authorization in Laravel is like having a hierarchical security system with different access levels. Users have roles (security clearance levels), and each role grants certain permissions (access to specific areas or operations). This approach simplifies authorization by grouping permissions into logical roles that align with user responsibilities.
API Authentication
Laravel offers several options for API authentication. Here we'll look at Laravel Sanctum, which provides a lightweight solution for API tokens, SPA authentication, and mobile application authentication:
Setting Up Sanctum
// Install Sanctum
composer require laravel/sanctum
// Publish configuration
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
// Run migrations
php artisan migrate
// In User model (typically already included)
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
// ...
}
Issuing API Tokens
// In a login controller
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
'device_name' => 'required',
]);
$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 abilities (scopes)
$token = $user->createToken($request->device_name, ['post:create', 'post:read'])->plainTextToken;
return response()->json(['token' => $token]);
}
// In a logout controller
public function logout(Request $request)
{
// Revoke the token that was used to authenticate the current request
$request->user()->currentAccessToken()->delete();
// Or revoke all tokens
// $request->user()->tokens()->delete();
return response()->json(['message' => 'Logged out']);
}
Protecting API Routes
// In routes/api.php
// All routes in this group require authentication
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
});
// Check for specific token abilities (scopes)
Route::middleware(['auth:sanctum', 'ability:post:create'])->post('/posts', [PostController::class, 'store']);
// Multiple abilities (any one required)
Route::middleware(['auth:sanctum', 'ability:post:create,post:update'])->put('/posts/{id}', [PostController::class, 'update']);
// Multiple abilities (all required)
Route::middleware(['auth:sanctum', 'abilities:post:create,post:update'])->put('/posts/{id}', [PostController::class, 'update']);
Consuming API with Token
// Example HTTP request
axios.get('/api/user', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
});
// Using fetch
fetch('/api/user', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
}).then(response => response.json());
Laravel Sanctum also supports authentication for SPAs (Single Page Applications) using cookies:
SPA Authentication with Sanctum
// In config/sanctum.php, configure the stateful domains
'stateful' => [
'localhost',
'localhost:3000',
'example.com',
'*.example.com',
],
// In routes/api.php
// Route for logging in
Route::post('/login', [AuthController::class, 'login']);
// CSRF Protection is required for cookie auth
// In JavaScript, you need to include the CSRF token
axios.defaults.withCredentials = true;
API authentication with Laravel Sanctum is like having a key card system that issues temporary access cards. Each token is like a personalized key card with specific access permissions, and the system verifies these cards for each API request, ensuring that only authorized users can access protected endpoints.
Practical Example: Complete Authentication System
Let's put these concepts together with a practical example of a complete authentication system for a blog application:
Models and Migrations
// User model with roles and permissions
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function hasRole($role)
{
return $this->roles->contains('name', $role);
}
public function posts()
{
return $this->hasMany(Post::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
}
// Post model with policies
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title', 'content', 'user_id', 'is_published'
];
public function user()
{
return $this->belongsTo(User::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
}
Policies
// Post Policy
class PostPolicy
{
/**
* Allow admins to do anything.
*/
public function before(User $user, $ability)
{
if ($user->hasRole('admin')) {
return true;
}
}
/**
* View any post.
*/
public function viewAny(User $user)
{
return true; // Anyone can view posts
}
/**
* View a specific post.
*/
public function view(User $user, Post $post)
{
// Anyone can view published posts
if ($post->is_published) {
return true;
}
// Authors can view their own unpublished posts
return $user->id === $post->user_id;
}
/**
* Create posts.
*/
public function create(User $user)
{
return $user->hasRole(['author', 'editor']);
}
/**
* Update a post.
*/
public function update(User $user, Post $post)
{
// Editors can edit any post
if ($user->hasRole('editor')) {
return true;
}
// Authors can edit their own posts
return $user->id === $post->user_id;
}
/**
* Delete a post.
*/
public function delete(User $user, Post $post)
{
// Only editors and the post owner can delete posts
return $user->hasRole('editor') || $user->id === $post->user_id;
}
/**
* Publish a post.
*/
public function publish(User $user, Post $post)
{
// Only editors can publish posts
return $user->hasRole('editor');
}
}
Controllers
// PostController with authorization
class PostController extends Controller
{
public function __construct()
{
$this->middleware('auth')->except(['index', 'show']);
$this->middleware('verified')->except(['index', 'show']);
}
/**
* Display a listing of posts.
*/
public function index()
{
$posts = Post::where('is_published', true)
->latest()
->paginate(10);
return view('posts.index', compact('posts'));
}
/**
* Show a specific post.
*/
public function show(Post $post)
{
$this->authorize('view', $post);
return view('posts.show', compact('post'));
}
/**
* Show the form for creating a post.
*/
public function create()
{
$this->authorize('create', Post::class);
return view('posts.create');
}
/**
* Store a newly created post.
*/
public function store(StorePostRequest $request)
{
$this->authorize('create', Post::class);
// All validation happens in the form request
$post = new Post($request->validated());
$post->user_id = auth()->id();
$post->save();
return redirect()->route('posts.show', $post)
->with('success', 'Post created successfully!');
}
/**
* Show the form for editing a post.
*/
public function edit(Post $post)
{
$this->authorize('update', $post);
return view('posts.edit', compact('post'));
}
/**
* Update the post.
*/
public function update(UpdatePostRequest $request, Post $post)
{
$this->authorize('update', $post);
$post->update($request->validated());
return redirect()->route('posts.show', $post)
->with('success', 'Post updated successfully!');
}
/**
* Delete the post.
*/
public function destroy(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
return redirect()->route('posts.index')
->with('success', 'Post deleted successfully!');
}
/**
* Publish a post.
*/
public function publish(Post $post)
{
$this->authorize('publish', $post);
$post->is_published = true;
$post->published_at = now();
$post->save();
return redirect()->route('posts.show', $post)
->with('success', 'Post published successfully!');
}
}
Form Requests
// StorePostRequest with validation
class StorePostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize()
{
return $this->user()->can('create', Post::class);
}
/**
* Get the validation rules for the request.
*/
public function rules()
{
return [
'title' => 'required|string|max:255',
'content' => 'required|string|min:50',
'category_id' => 'required|exists:categories,id',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
];
}
}
Routes
// Web routes with authentication and authorization
// Public routes
Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
// Authentication routes (provided by Breeze)
Route::middleware('guest')->group(function () {
Route::get('/login', [AuthenticatedSessionController::class, 'create'])->name('login');
Route::post('/login', [AuthenticatedSessionController::class, 'store']);
Route::get('/register', [RegisteredUserController::class, 'create'])->name('register');
Route::post('/register', [RegisteredUserController::class, 'store']);
Route::get('/forgot-password', [PasswordResetLinkController::class, 'create'])->name('password.request');
Route::post('/forgot-password', [PasswordResetLinkController::class, 'store'])->name('password.email');
Route::get('/reset-password/{token}', [NewPasswordController::class, 'create'])->name('password.reset');
Route::post('/reset-password', [NewPasswordController::class, 'store'])->name('password.update');
});
// Authenticated routes
Route::middleware('auth')->group(function () {
Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout');
Route::get('/email/verify', [EmailVerificationPromptController::class, '__invoke'])->name('verification.notice');
Route::get('/email/verify/{id}/{hash}', [VerifyEmailController::class, '__invoke'])->middleware(['signed', 'throttle:6,1'])->name('verification.verify');
Route::post('/email/verification-notification', [EmailVerificationNotificationController::class, 'store'])->middleware('throttle:6,1')->name('verification.send');
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::put('/profile', [ProfileController::class, 'update'])->name('profile.update');
// Post management routes
Route::get('/posts/create', [PostController::class, 'create'])->name('posts.create');
Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
Route::get('/posts/{post}/edit', [PostController::class, 'edit'])->name('posts.edit');
Route::put('/posts/{post}', [PostController::class, 'update'])->name('posts.update');
Route::delete('/posts/{post}', [PostController::class, 'destroy'])->name('posts.destroy');
// Publish action (editor only)
Route::put('/posts/{post}/publish', [PostController::class, 'publish'])->name('posts.publish');
});
// Admin routes
Route::middleware(['auth', 'role:admin'])->prefix('admin')->name('admin.')->group(function () {
Route::get('/dashboard', [AdminController::class, 'index'])->name('dashboard');
Route::resource('users', AdminUserController::class);
Route::resource('roles', AdminRoleController::class);
});
Blade Templates
// post/show.blade.php with authorization
@extends('layouts.app')
@section('content')
<div class="container">
@if(session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
<div class="post">
<h1>{{ $post->title }}</h1>
<div class="post-meta">
<span>By {{ $post->user->name }} | {{ $post->created_at->format('F j, Y') }}</span>
@unless($post->is_published)
<span class="badge bg-warning">Unpublished</span>
@endunless
</div>
<div class="post-actions">
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}" class="btn btn-sm btn-primary">Edit</a>
@endcan
@can('delete', $post)
<form action="{{ route('posts.destroy', $post) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure?')">Delete</button>
</form>
@endcan
@if(!$post->is_published)
@can('publish', $post)
<form action="{{ route('posts.publish', $post) }}" method="POST" class="d-inline">
@csrf
@method('PUT')
<button type="submit" class="btn btn-sm btn-success">Publish</button>
</form>
@endcan
@endif
</div>
<div class="post-content">
{{ $post->content }}
</div>
<div class="comments-section">
<h3>Comments</h3>
@auth
<form action="{{ route('comments.store') }}" method="POST">
@csrf
<input type="hidden" name="post_id" value="{{ $post->id }}">
<div class="form-group">
<textarea name="content" class="form-control @error('content') is-invalid @enderror" rows="3" placeholder="Leave a comment">{{ old('content') }}</textarea>
@error('content')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-primary">Submit Comment</button>
</form>
@else
<p><a href="{{ route('login') }}">Log in</a> to leave a comment.</p>
@endauth
<div class="comments-list">
@foreach($post->comments as $comment)
<div class="comment">
<div class="comment-meta">
<strong>{{ $comment->user->name }}</strong> • {{ $comment->created_at->diffForHumans() }}
</div>
<div class="comment-content">
{{ $comment->content }}
</div>
@can('delete', $comment)
<form action="{{ route('comments.destroy', $comment) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-text text-danger">Delete</button>
</form>
@endcan
</div>
@endforeach
</div>
</div>
</div>
</div>
@endsection
Navigation with Authorization
// nav.blade.php with authorization
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="{{ route('home') }}">BlogApp</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ route('home') }}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('posts.index') }}">Posts</a>
</li>
@can('create', App\Models\Post::class)
<li class="nav-item">
<a class="nav-link" href="{{ route('posts.create') }}">Create Post</a>
</li>
@endcan
@if(auth()->user() && auth()->user()->hasRole('admin'))
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.dashboard') }}">Admin Dashboard</a>
</li>
@endif
</ul>
<ul class="navbar-nav">
@guest
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">Register</a>
</li>
@else
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown">
{{ auth()->user()->name }}
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="{{ route('profile.edit') }}">Profile</a>
</li>
<li>
<hr class="dropdown-divider">
</li>
<li>
<form action="{{ route('logout') }}" method="POST">
@csrf
<button type="submit" class="dropdown-item">Logout</button>
</form>
</li>
</ul>
</li>
@endguest
</ul>
</div>
</div>
</nav>
This practical example demonstrates a complete authentication and authorization system for a blog application, integrating user authentication, role-based permissions, policies for fine-grained access control, and secure form handling.
Practical Activity: User Roles and Permissions
Let's solidify your understanding with a hands-on activity:
Activity: Implement a Role and Permission System
-
Set up the database structure:
- Create migration for roles, permissions, and pivot tables
- Add role and permission models with relationships
- Add relationship methods to the User model
-
Implement authorization logic:
- Create middleware for role and permission checks
- Define gates for common permissions in AuthServiceProvider
- Create policies for model-specific authorization
-
Create an admin interface:
- Build CRUD controllers for roles and permissions
- Create forms for assigning roles to users
- Implement views with proper authorization checks
-
Add a dashboard with role-specific content:
- Create a dashboard controller and view
- Display different content based on user roles
- Implement navigation that shows only accessible items
Extension: Add a permission system that allows dynamic assignment of permissions to roles and direct assignment of permissions to users.
Best Practices for Authentication
Follow these best practices to ensure your authentication system is secure, user-friendly, and maintainable:
Security
- Always use HTTPS in production environments
- Implement throttling for login attempts to prevent brute force attacks
- Use secure password reset mechanisms
- Regularly audit and rotate API tokens
- Implement two-factor authentication for sensitive operations
- Always regenerate session IDs on login and state changes
User Experience
- Provide clear error messages for login failures
- Implement "remember me" functionality
- Redirect users to their intended destination after login
- Make password requirements clear during registration
- Provide account recovery options besides password reset
- Consider social login options for convenience
Authorization Design
- Use policies for model-specific authorization logic
- Implement role-based access control for group permissions
- Design permissions to be granular but not overwhelming
- Regularly audit authorization rules
- Default to denying access unless explicitly granted
- Document your authorization system for other developers
Maintenance
- Keep authentication packages updated
- Write tests for authentication and authorization logic
- Use dependency injection for authorization services
- Regularly review and prune inactive user accounts
- Log important authentication events
Following these best practices ensures that your authentication and authorization system is not only secure but also provides a positive user experience and remains maintainable as your application grows and evolves.
Two-Factor Authentication
Two-factor authentication (2FA) adds an extra layer of security by requiring a second form of identification beyond just a password. Laravel Fortify and Jetstream include built-in support for two-factor authentication:
Enabling Two-Factor Authentication with Jetstream
// In config/jetstream.php
'features' => [
// ...
Features::twoFactorAuthentication([
'confirmPassword' => true,
]),
],
Implementing Custom Two-Factor Authentication
// Migration for 2FA fields
Schema::table('users', function (Blueprint $table) {
$table->text('two_factor_secret')
->after('password')
->nullable();
$table->text('two_factor_recovery_codes')
->after('two_factor_secret')
->nullable();
$table->timestamp('two_factor_confirmed_at')
->after('two_factor_recovery_codes')
->nullable();
});
// Using a package like pragmarx/google2fa for TOTP generation
composer require pragmarx/google2fa
// In controller for enabling 2FA
public function enableTwoFactor(Request $request)
{
$user = $request->user();
// Generate a secret
$google2fa = app('pragmarx.google2fa');
$secret = $google2fa->generateSecretKey();
// Save to user record
$user->two_factor_secret = encrypt($secret);
$user->save();
// Generate QR code URL
$qrCodeUrl = $google2fa->getQRCodeUrl(
config('app.name'),
$user->email,
$secret
);
return view('auth.two-factor.enable', [
'qrCodeUrl' => $qrCodeUrl,
'secret' => $secret,
]);
}
// Confirming 2FA setup with user-provided code
public function confirmTwoFactor(Request $request)
{
$request->validate([
'code' => 'required|string|size:6',
]);
$user = $request->user();
$google2fa = app('pragmarx.google2fa');
$secret = decrypt($user->two_factor_secret);
if ($google2fa->verifyKey($secret, $request->code)) {
$user->two_factor_confirmed_at = now();
// Generate recovery codes
$user->two_factor_recovery_codes = encrypt(json_encode(
Collection::times(8, function () {
return Str::random(10).'-'.Str::random(10);
})->all()
));
$user->save();
return redirect()->route('profile.show')
->with('status', 'Two factor authentication enabled.');
}
return back()->withErrors([
'code' => 'The provided code was invalid.',
]);
}
// In login controller, add 2FA challenge after password authentication
protected function authenticated(Request $request, $user)
{
if ($user->two_factor_confirmed_at) {
// Logout but remember the user ID
auth()->logout();
// Store user ID in session for 2FA challenge
$request->session()->put('auth.2fa.user_id', $user->id);
return redirect()->route('two-factor.challenge');
}
return redirect()->intended($this->redirectPath());
}
Two-factor authentication is like having both a key and a security code for a safety deposit box. Even if someone steals your key (password), they still can't access the contents without the constantly changing security code from your authenticator app.
Social Authentication
Social authentication allows users to sign in using their existing accounts with services like Google, Facebook, GitHub, etc. Laravel Socialite provides a simple interface for social authentication:
Setting Up Socialite
// Install Laravel Socialite
composer require laravel/socialite
// In config/services.php, add OAuth credentials
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => 'http://example.com/login/github/callback',
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => 'http://example.com/login/google/callback',
],
Implementing Social Login
// In routes/web.php
Route::get('/login/{provider}', [SocialLoginController::class, 'redirect'])->name('social.redirect');
Route::get('/login/{provider}/callback', [SocialLoginController::class, 'callback'])->name('social.callback');
// SocialLoginController
class SocialLoginController extends Controller
{
/**
* Redirect to provider for authentication.
*/
public function redirect($provider)
{
// Validate provider
if (!in_array($provider, ['github', 'google', 'facebook'])) {
return redirect()->route('login');
}
return Socialite::driver($provider)->redirect();
}
/**
* Handle provider callback.
*/
public function callback($provider)
{
try {
$socialUser = Socialite::driver($provider)->user();
} catch (\Exception $e) {
return redirect()->route('login')->withErrors([
'email' => 'Social login failed. Please try again.',
]);
}
// 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(), // Social logins are pre-verified
]);
}
// Check if this social login is linked to user
$socialAccount = SocialAccount::where('provider', $provider)
->where('provider_id', $socialUser->getId())
->first();
if (!$socialAccount) {
// Link account
SocialAccount::create([
'user_id' => $user->id,
'provider' => $provider,
'provider_id' => $socialUser->getId(),
]);
}
// Login user
auth()->login($user, true);
return redirect()->intended('/dashboard');
}
}
Social Account Model
// Create a model and migration for social accounts
php artisan make:model SocialAccount -m
// Migration
Schema::create('social_accounts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('provider');
$table->string('provider_id');
$table->timestamps();
$table->unique(['provider', 'provider_id']);
});
// SocialAccount model
class SocialAccount extends Model
{
protected $fillable = [
'user_id',
'provider',
'provider_id',
];
public function user()
{
return $this->belongsTo(User::class);
}
}
Adding Social Login Buttons to Login Form
<form method="POST" action="{{ route('login') }}">
@csrf
<!-- Regular login form fields -->
<div class="social-auth-links text-center mt-4">
<p>- OR -</p>
<a href="{{ route('social.redirect', 'github') }}" class="btn btn-block btn-github">
<i class="fab fa-github"></i> Sign in with GitHub
</a>
<a href="{{ route('social.redirect', 'google') }}" class="btn btn-block btn-google">
<i class="fab fa-google"></i> Sign in with Google
</a>
</div>
</form>
Social authentication is like using a trusted third party to vouch for your identity. Instead of creating and remembering another username and password, users can leverage an existing trusted relationship with a major service provider to quickly access your application.
Testing Authentication
Testing your authentication system ensures it works correctly and remains secure as your application evolves. Laravel's testing features make it easy to test authentication functionality:
Feature Tests for Authentication
// Generate a test
php artisan make:test AuthenticationTest
// Basic authentication test
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AuthenticationTest extends TestCase
{
use RefreshDatabase;
public function test_login_screen_can_be_rendered()
{
$response = $this->get('/login');
$response->assertStatus(200);
}
public function test_users_can_authenticate_using_the_login_screen()
{
$user = User::factory()->create();
$response = $this->post('/login', [
'email' => $user->email,
'password' => 'password',
]);
$this->assertAuthenticated();
$response->assertRedirect('/dashboard');
}
public function test_users_can_not_authenticate_with_invalid_password()
{
$user = User::factory()->create();
$this->post('/login', [
'email' => $user->email,
'password' => 'wrong-password',
]);
$this->assertGuest();
}
public function test_users_can_logout()
{
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/logout');
$this->assertGuest();
$response->assertRedirect('/');
}
}
Authorization Tests
// Policy test
class PostPolicyTest extends TestCase
{
use RefreshDatabase;
protected $admin;
protected $editor;
protected $author;
protected $user;
public function setUp(): void
{
parent::setUp();
// Create users with different roles
$this->admin = User::factory()->create();
$this->admin->roles()->attach(Role::where('name', 'admin')->first());
$this->editor = User::factory()->create();
$this->editor->roles()->attach(Role::where('name', 'editor')->first());
$this->author = User::factory()->create();
$this->author->roles()->attach(Role::where('name', 'author')->first());
$this->user = User::factory()->create();
}
public function test_admin_can_edit_any_post()
{
$post = Post::factory()->create([
'user_id' => $this->author->id
]);
$this->assertTrue($this->admin->can('update', $post));
}
public function test_editor_can_edit_any_post()
{
$post = Post::factory()->create([
'user_id' => $this->author->id
]);
$this->assertTrue($this->editor->can('update', $post));
}
public function test_author_can_edit_own_post()
{
$post = Post::factory()->create([
'user_id' => $this->author->id
]);
$this->assertTrue($this->author->can('update', $post));
}
public function test_author_cannot_edit_others_post()
{
$post = Post::factory()->create([
'user_id' => $this->editor->id
]);
$this->assertFalse($this->author->can('update', $post));
}
public function test_regular_user_cannot_edit_any_post()
{
$post = Post::factory()->create();
$this->assertFalse($this->user->can('update', $post));
}
public function test_only_editor_can_publish_post()
{
$post = Post::factory()->create([
'user_id' => $this->author->id,
'is_published' => false
]);
$this->assertTrue($this->editor->can('publish', $post));
$this->assertFalse($this->author->can('publish', $post));
$this->assertFalse($this->user->can('publish', $post));
}
}
Test Protected Routes
class RouteAuthorizationTest extends TestCase
{
use RefreshDatabase;
public function test_dashboard_requires_authentication()
{
$response = $this->get('/dashboard');
$response->assertRedirect('/login');
}
public function test_authenticated_user_can_access_dashboard()
{
$user = User::factory()->create();
$response = $this->actingAs($user)->get('/dashboard');
$response->assertStatus(200);
}
public function test_admin_pages_require_admin_role()
{
$user = User::factory()->create();
$response = $this->actingAs($user)->get('/admin/dashboard');
$response->assertStatus(403);
// Create admin user
$admin = User::factory()->create();
$admin->roles()->attach(Role::where('name', 'admin')->first());
$response = $this->actingAs($admin)->get('/admin/dashboard');
$response->assertStatus(200);
}
}
Testing authentication is like regularly checking the security systems in a building. By systematically testing each component, you ensure that the entire system works correctly, identifies authorized users, keeps out unauthorized ones, and enforces access rules appropriately.
Advanced Authentication Scenarios
Beyond basic authentication, Laravel supports several advanced authentication scenarios:
HTTP Basic Authentication
Simple authentication without session cookies:
Route::get('/api/user-info', function () {
// Only for authenticated users with HTTP Basic Auth
})->middleware('auth.basic');
Single Session Authentication
Ensure users have only one active session:
Route::middleware(['auth', 'auth.session'])->group(function () {
// Routes that enforce single session
});
Stateless API Authentication
Authentication for APIs without sessions:
Route::middleware('auth:sanctum')->get('/api/user', function (Request $request) {
return $request->user();
});
Multi-Tenant Authentication
Authentication specific to application tenants:
// In a middleware
public function handle($request, $next)
{
// Determine tenant from subdomain
$tenant = Tenant::where('domain', $request->getHost())->first();
if (!$tenant) {
abort(404);
}
// Set tenant in the application
app()->instance('tenant', $tenant);
// Switch to tenant's database
config(['database.connections.tenant.database' => $tenant->database]);
DB::purge('tenant');
// Continue request
return $next($request);
}
Advanced authentication scenarios address specific security and architectural requirements for different types of applications, from APIs to multi-tenant systems.
Summary and Key Takeaways
- Laravel provides a comprehensive authentication system that handles user registration, login, password reset, and email verification
- Authentication scaffolding options like Breeze, Jetstream, and Fortify provide different levels of features and frontend integration
- Guards and providers allow you to customize how authentication works for different types of users and storage mechanisms
- Laravel's authorization system with Gates and Policies provides fine-grained access control for your application's resources
- Role-based authorization simplifies permission management by grouping related permissions
- Laravel Sanctum offers a lightweight solution for API authentication, including token-based and SPA authentication
- Advanced features like two-factor authentication and social login enhance security and user experience
- Testing authentication and authorization ensures your security systems remain effective as your application evolves
With Laravel's authentication and authorization features, you can build secure, user-friendly applications that properly protect sensitive operations and data.
Further Resources
- Laravel Authentication Documentation
- Laravel Authorization Documentation
- Laravel Sanctum Documentation
- Laravel Socialite Documentation
- Spatie Laravel Permission Package Documentation
- "Laravel Up & Running" by Matt Stauffer (Chapter on Authentication)
- "Laravel: The Right Way" by Taylor Otwell and Jeffrey Way (Chapters on Auth)