Learn how to protect routes with permission and role middleware
Laravel Permission provides middleware to protect your routes based on roles and permissions. This ensures that only authorized users can access specific routes.
use Spatie\Permission\Middleware\RoleMiddleware;// Single roleRoute::get('/admin', [AdminController::class, 'index']) ->middleware(RoleMiddleware::using('admin'));// Multiple rolesRoute::get('/content', [ContentController::class, 'index']) ->middleware(RoleMiddleware::using(['writer', 'editor']));// With specific guardRoute::get('/admin', [AdminController::class, 'index']) ->middleware(RoleMiddleware::using('admin', 'api'));
// From: src/Middleware/PermissionMiddleware.php:16-42public function handle(Request $request, Closure $next, $permission, ?string $guard = null){ $authGuard = Auth::guard($guard); $user = $authGuard->user(); // Check if user is logged in if (!$user) { throw UnauthorizedException::notLoggedIn(); } // Check if user has the HasRoles trait if (!method_exists($user, 'hasAnyPermission')) { throw UnauthorizedException::missingTraitHasRoles($user); } // Parse permissions (handles pipe-separated list) $permissions = explode('|', $permission); // Check if user has ANY of the permissions if (!$user->canAny($permissions)) { throw UnauthorizedException::forPermissions($permissions); } return $next($request);}
When middleware denies access, it throws an UnauthorizedException:
namespace App\Exceptions;use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;use Spatie\Permission\Exceptions\UnauthorizedException;class Handler extends ExceptionHandler{ public function register() { $this->renderable(function (UnauthorizedException $e, $request) { if ($request->expectsJson()) { return response()->json([ 'error' => 'You do not have permission to access this resource.' ], 403); } return redirect()->route('home') ->with('error', 'You do not have permission to access this resource.'); }); }}
Without custom handling, unauthorized access returns a 403 Forbidden response.
The middleware supports Laravel Passport machine-to-machine authentication:
// Automatically checks Passport clients when no user is authenticated// This is handled in the middleware's handle() methodif (!$user && $request->bearerToken() && config('permission.use_passport_client_credentials')) { $user = Guard::getPassportClient($guard);}
Layer your protection: Use middleware for route protection AND check permissions in controllers/policies for additional security:
// Route protection (first line of defense)Route::put('/articles/{article}', [ArticleController::class, 'update']) ->middleware('permission:edit articles');// Controller check (second line)public function update(Article $article){ $this->authorize('update', $article); // ...}
Use permissions over roles: Prefer permission middleware over role middleware for better flexibility:
// Good - flexible as role permissions changeRoute::middleware('permission:edit articles')// Less flexible - tied to specific rolesRoute::middleware('role:editor')
Don’t forget authentication: Ensure routes have auth middleware before permission/role middleware: