Skip to main content
Laravel Permission provides middleware to protect your routes based on roles and permissions. This ensures that only authorized users can access specific routes.

Available Middleware

The package provides three middleware classes:

PermissionMiddleware

Check user permissions

RoleMiddleware

Check user roles

RoleOrPermissionMiddleware

Check either role or permission

Registering Middleware

First, register the middleware in your bootstrap/app.php file:
// bootstrap/app.php
use Spatie\Permission\Middleware\PermissionMiddleware;
use Spatie\Permission\Middleware\RoleMiddleware;
use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->alias([
            'role' => RoleMiddleware::class,
            'permission' => PermissionMiddleware::class,
            'role_or_permission' => RoleOrPermissionMiddleware::class,
        ]);
    })
    ->create();

Permission Middleware

Protect routes based on user permissions.
Require a specific permission:
use App\Http\Controllers\ArticleController;

Route::get('/articles/edit/{article}', [ArticleController::class, 'edit'])
    ->middleware('permission:edit articles');
If the user doesn’t have the permission, a 403 Unauthorized exception is thrown.

Using the Static Method

For more complex scenarios, use the static using() method:
use Spatie\Permission\Middleware\PermissionMiddleware;

// Single permission
Route::get('/articles', [ArticleController::class, 'index'])
    ->middleware(PermissionMiddleware::using('view articles'));

// Multiple permissions
Route::get('/articles', [ArticleController::class, 'index'])
    ->middleware(PermissionMiddleware::using(['view articles', 'edit articles']));

// With specific guard
Route::get('/articles', [ArticleController::class, 'index'])
    ->middleware(PermissionMiddleware::using('view articles', 'api'));

Role Middleware

Protect routes based on user roles.
Require a specific role:
Route::get('/admin/dashboard', [AdminController::class, 'dashboard'])
    ->middleware('role:admin');

Using the Static Method

use Spatie\Permission\Middleware\RoleMiddleware;

// Single role
Route::get('/admin', [AdminController::class, 'index'])
    ->middleware(RoleMiddleware::using('admin'));

// Multiple roles
Route::get('/content', [ContentController::class, 'index'])
    ->middleware(RoleMiddleware::using(['writer', 'editor']));

// With specific guard
Route::get('/admin', [AdminController::class, 'index'])
    ->middleware(RoleMiddleware::using('admin', 'api'));

Role or Permission Middleware

Allow access if user has either a specific role OR permission.
// User needs role 'admin' OR permission 'edit articles'
Route::put('/articles/{article}', [ArticleController::class, 'update'])
    ->middleware('role_or_permission:admin|edit articles');

// Multiple options
Route::get('/content/manage', [ContentController::class, 'manage'])
    ->middleware('role_or_permission:admin|editor|manage content');
This middleware checks both roles and permissions. Access is granted if the user has ANY of the specified roles OR permissions.

Using the Static Method

use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;

Route::get('/special', [SpecialController::class, 'index'])
    ->middleware(RoleOrPermissionMiddleware::using(['admin', 'special-access']));

Route Groups

Apply middleware to multiple routes at once:
Route::middleware(['permission:edit articles'])->group(function () {
    Route::get('/articles/create', [ArticleController::class, 'create']);
    Route::post('/articles', [ArticleController::class, 'store']);
    Route::get('/articles/{article}/edit', [ArticleController::class, 'edit']);
    Route::put('/articles/{article}', [ArticleController::class, 'update']);
});

Using Route Macros

The package provides convenient route macros:
// Single role
Route::get('/admin/dashboard', [AdminController::class, 'dashboard'])
    ->role('admin');

// Multiple roles
Route::get('/content', [ContentController::class, 'index'])
    ->role(['writer', 'editor']);
Route macros are defined in PermissionServiceProvider.php:90-114 and provide a cleaner syntax.

Controller Middleware

Apply middleware in your controller constructor:
namespace App\Http\Controllers;

use App\Http\Controllers\Controller;

class ArticleController extends Controller
{
    public function __construct()
    {
        // Apply to all methods
        $this->middleware('permission:view articles');
        
        // Apply to specific methods
        $this->middleware('permission:edit articles')
             ->only(['create', 'store', 'edit', 'update']);
        
        // Except certain methods
        $this->middleware('permission:delete articles')
             ->except(['index', 'show']);
        
        // Multiple middleware
        $this->middleware('role:admin|editor')
             ->only(['publish', 'unpublish']);
    }
}

How Middleware Works

Let’s look at the internal logic:
// From: src/Middleware/PermissionMiddleware.php:16-42

public 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);
}

Exception Handling

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.

Passport Client Credentials

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() method

if (!$user && $request->bearerToken() && config('permission.use_passport_client_credentials')) {
    $user = Guard::getPassportClient($guard);
}
Enable in config:
'use_passport_client_credentials' => true,

Best Practices

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 change
Route::middleware('permission:edit articles')

// Less flexible - tied to specific roles
Route::middleware('role:editor')
Don’t forget authentication: Ensure routes have auth middleware before permission/role middleware:
Route::middleware(['auth', 'permission:edit articles'])->group(function () {
    // Routes
});

Common Patterns

// routes/api.php
Route::middleware(['auth:sanctum'])->group(function () {
    Route::middleware(['permission:view articles,api'])->group(function () {
        Route::get('/articles', [ApiArticleController::class, 'index']);
    });
    
    Route::middleware(['permission:edit articles,api'])->group(function () {
        Route::post('/articles', [ApiArticleController::class, 'store']);
        Route::put('/articles/{article}', [ApiArticleController::class, 'update']);
    });
});
Route::prefix('admin')
    ->middleware(['auth', 'role:admin'])
    ->group(function () {
        Route::get('/dashboard', [AdminController::class, 'dashboard']);
        
        // User management
        Route::middleware(['permission:manage users'])->group(function () {
            Route::resource('users', UserController::class);
        });
        
        // Settings (super admin only)
        Route::middleware(['permission:manage settings'])->group(function () {
            Route::get('/settings', [SettingsController::class, 'edit']);
            Route::put('/settings', [SettingsController::class, 'update']);
        });
    });
Route::resource('articles', ArticleController::class)
    ->middleware([
        'index' => 'permission:view articles',
        'show' => 'permission:view articles', 
        'create' => 'permission:create articles',
        'store' => 'permission:create articles',
        'edit' => 'permission:edit articles',
        'update' => 'permission:edit articles',
        'destroy' => 'permission:delete articles',
    ]);

Next Steps

Multiple Guards

Use permissions with different authentication guards

Events

Handle permission and role events

Build docs developers (and LLMs) love