Middleware vs Gates/Policies
Learn when to use Middleware for authentication vs Gates/Policies for authorization
Middleware and Gates/Policies both control access, but they serve different purposes.
Middleware - HTTP Request Filtering
• Runs BEFORE the controller
• Checks if a user CAN MAKE this type of request
• Examples: Is user logged in? Is email verified? Is request rate-limited?
• Answers: "Is this user allowed to access this route at all?"
Gates/Policies - Business Logic Authorization
• Runs INSIDE your controller logic
• Checks if a user CAN PERFORM an action on a SPECIFIC resource
• Examples: Can this user edit THIS post? Can this user delete THIS comment?
• Answers: "Is this user allowed to perform this action on this specific item?"
Quick Decision Guide:
Use Middleware when: Checking user status (logged in, email verified, subscription active)
Use Gates/Policies when: Checking permissions on specific models (can edit this post, can delete this comment)
Real-World Example:
Blog System:
• Middleware: Ensure user is authenticated (auth middleware)
• Policy: Check if THIS user can edit THIS specific blog post (they must be the author or admin)
Middleware - HTTP Request Filtering
• Runs BEFORE the controller
• Checks if a user CAN MAKE this type of request
• Examples: Is user logged in? Is email verified? Is request rate-limited?
• Answers: "Is this user allowed to access this route at all?"
Gates/Policies - Business Logic Authorization
• Runs INSIDE your controller logic
• Checks if a user CAN PERFORM an action on a SPECIFIC resource
• Examples: Can this user edit THIS post? Can this user delete THIS comment?
• Answers: "Is this user allowed to perform this action on this specific item?"
Quick Decision Guide:
Use Middleware when: Checking user status (logged in, email verified, subscription active)
Use Gates/Policies when: Checking permissions on specific models (can edit this post, can delete this comment)
Real-World Example:
Blog System:
• Middleware: Ensure user is authenticated (auth middleware)
• Policy: Check if THIS user can edit THIS specific blog post (they must be the author or admin)
Code Examples
Middleware - Request-Level Control
✓ Best Practice
php
<?php
// Middleware checks if user CAN ACCESS the route
// app/Http/Middleware/EnsureUserIsSubscribed.php
class EnsureUserIsSubscribed
{
public function handle(Request $request, Closure $next)
{
// Check user's subscription status (not a specific resource)
if (!$request->user() || !$request->user()->isSubscribed()) {
return redirect('subscribe')
->with('error', 'You need an active subscription');
}
// User is subscribed, continue to controller
return $next($request);
}
}
// Usage in routes:
Route::middleware(['auth', 'subscribed'])->group(function () {
Route::get('/premium/courses', [CourseController::class, 'index']);
Route::get('/premium/videos', [VideoController::class, 'index']);
Route::get('/premium/downloads', [DownloadController::class, 'index']);
});
// Middleware runs BEFORE any controller
// It doesn't care about WHICH course or video
// Only cares about: "Is user subscribed?"
// Use Cases for Middleware:
// ✅ Authentication (is user logged in?)
// ✅ Email verification (is email verified?)
// ✅ Subscription status (is subscription active?)
// ✅ Rate limiting (has user exceeded API limits?)
// ✅ Role checking (is user an admin?) - broad check
💡 Middleware runs before the controller and checks request-level access. It doesn't know about specific resources, only about the user's general status.
Policies - Resource-Level Control
✓ Best Practice
php
<?php
// Policy checks if user CAN PERFORM ACTION on SPECIFIC resource
// app/Policies/PostPolicy.php
class PostPolicy
{
// Can this user update THIS specific post?
public function update(User $user, Post $post): bool
{
// Only the author or admins can update
return $user->id === $post->user_id
|| $user->isAdmin();
}
// Can this user delete THIS specific post?
public function delete(User $user, Post $post): bool
{
// Only admins can delete
return $user->isAdmin();
}
// Can this user publish THIS specific post?
public function publish(User $user, Post $post): bool
{
// Only the author can publish their own draft
return $user->id === $post->user_id
&& $post->status === 'draft';
}
}
// Usage in controller:
class PostController extends Controller
{
public function update(Request $request, Post $post)
{
// Check authorization for THIS specific post
$this->authorize('update', $post);
// If we reach here, user is authorized
$post->update($request->validated());
return redirect()->route('posts.show', $post);
}
}
// Policy runs INSIDE the controller
// It knows about the SPECIFIC post being updated
// Answers: "Can THIS user update THIS post?"
// Use Cases for Policies:
// ✅ Editing specific models (can edit THIS post?)
// ✅ Deleting specific resources (can delete THIS comment?)
// ✅ Viewing private content (can view THIS draft?)
// ✅ Resource ownership (is THIS user the owner?)
💡 Policies check permissions on specific model instances. They run inside your controller and have access to both the user and the specific resource being accessed.
Gates - Simple Authorization Checks
💡 Solution
php
<?php
// Gates are for simple, non-model authorization
// app/Providers/AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Define a gate for admin access
Gate::define('access-admin-panel', function (User $user) {
return $user->role === 'admin';
});
// Define a gate for feature flags
Gate::define('use-beta-features', function (User $user) {
return $user->beta_tester || $user->isAdmin();
});
// Define a gate for time-based access
Gate::define('post-after-hours', function (User $user) {
$hour = now()->hour;
return $user->isAdmin() || ($hour >= 9 && $hour <= 17);
});
}
}
// Usage in controllers:
class AdminController extends Controller
{
public function index()
{
// Simple check not tied to a specific model
if (Gate::denies('access-admin-panel')) {
abort(403, 'Access denied');
}
return view('admin.dashboard');
}
}
// Usage in Blade views:
@can('use-beta-features')
<a href="/beta/new-editor">Try Beta Editor</a>
@endcan
// Gates vs Policies:
// Gates: Simple, not tied to models
// Policies: Complex, tied to specific model instances
💡 Gates are for simple authorization that isn't tied to a specific model. Use them for general permissions like "access admin panel" or "use beta features".
Combining Middleware + Policies (Real Example)
💡 Solution
php
<?php
// Real-world blog platform example
// Step 1: Route with middleware (request-level check)
Route::middleware(['auth', 'verified'])->group(function () {
Route::resource('posts', PostController::class);
});
// Middleware asks: "Is user logged in and verified?"
// Step 2: Controller with policy (resource-level check)
class PostController extends Controller
{
public function edit(Post $post)
{
// Policy asks: "Can THIS user edit THIS specific post?"
$this->authorize('update', $post);
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$post->update($request->validated());
return redirect()->route('posts.show', $post);
}
public function destroy(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
return redirect()->route('posts.index');
}
}
// Security layers:
// Layer 1 (Middleware): Is user authenticated and verified?
// Layer 2 (Policy): Can THIS user modify THIS post?
// This is the Laravel way:
// ✅ Middleware for broad access control
// ✅ Policies for specific resource permissions
// ✅ Together they create a secure system
// Timeline of a request to edit a post:
// 1. Middleware checks if user is logged in → ✓
// 2. Middleware checks if email is verified → ✓
// 3. Controller loads the specific post → ✓
// 4. Policy checks if THIS user can edit THIS post → ✓
// 5. Edit view is shown
💡 In real applications, you combine middleware (broad access) with policies (specific permissions). Middleware runs first to filter requests, then policies check specific resource permissions.