How to add multiple roles to a route group laravel

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Multi-Role Route Protection in Laravel: A Deep Dive

As developers building complex applications on Laravel, role-based access control (RBAC) is a fundamental requirement. We often need to protect entire sections of our application—like an administrative dashboard—based on whether a user possesses one or more specific roles. The common approach using simple middleware like role:Admin works perfectly for single permissions, but what happens when we need a route to be accessible by users who are either 'SuperAdmin' OR 'Moderator'?

This post dives deep into the challenge of applying multiple, mutually exclusive roles to a single route group in Laravel and provides a robust, custom solution.

The Challenge: Limitations of Standard Route Middleware

When we attempt to use pipe-separated values directly in standard middleware syntax, such as Route::middleware(['role:SuperAdmin|Admin']), Laravel's default role middleware often fails to parse this complex logical condition correctly. It typically expects a single role name or uses an AND logic implicitly, making it impossible to define an OR relationship for multiple roles within the route definition itself.

This limitation forces us to move beyond simple built-in middleware and implement custom logic that inspects the authenticated user against a list of required roles. This is where we leverage Laravel's powerful extensibility to build truly flexible authorization systems.

The Solution: Customizing Role Middleware

To handle complex, multi-role requirements efficiently, the best practice is to create a custom middleware that handles the parsing and validation logic internally. This allows us to define our route simply, while the middleware handles the heavy lifting of checking permissions against the user's entitlements stored in the database.

The core of this solution involves three main components:

  1. Database Setup: Defining the many-to-many relationship between users and roles (e.g., users_roles).
  2. User Model Trait: Extending the User model to include methods that can check for the existence of multiple roles.
  3. Custom Middleware: The logic layer that parses the input string (SuperAdmin|Admin) and validates if the user possesses any of those specified roles.

Step 1: Enhancing the User Model with Role Logic

We start by ensuring our User model can query their relationships effectively. By utilizing traits, we keep our authorization logic clean and reusable.

use App\Models\Role; // Assuming you have a Role model
use Illuminate\Auth\Authenticatable;
use Illuminate\Support\Facades\Hash;

class User extends Authenticatable
{
    use Notifiable, HasPermissionsTrait, Billable;
    // ... other traits and properties
}

Inside the HasPermissionsTrait, we implement a method that checks if the user has any of the provided roles. This is crucial for handling the "OR" condition required by route groups.

trait HasPermissionsTrait
{
    /**
     * Checks if the user has at least one of the specified roles.
     */
    public function hasAnyRole(array $roles): bool
    {
        if (empty($roles)) {
            return true; // No roles required, access granted (or handle as an error)
        }

        // Check if the user belongs to any of the provided role IDs/names
        $roleNames = $this->roles()->pluck('name')->toArray();

        foreach ($roles as $requiredRole) {
            if (in_array($requiredRole, $roleNames)) {
                return true; // Found a match!
            }
        }

        return false;
    }

    // ... other methods like roles() relationship definition
}

Step 2: Implementing the Multi-Role Middleware

Now we create our custom middleware. This middleware will intercept the request, extract the pipe-separated roles from the route definition, and use the enhanced logic in the User model to perform the authorization check.

In your custom middleware file (e.g., RoleMiddleware.php), you would parse the input string:

public function handle($request, Closure $next, $rolesString)
{
    // 1. Parse the roles string separated by '|'
    $allowedRoles = array_map('trim', explode('|', $rolesString));

    if (empty($allowedRoles)) {
        abort(403, 'No roles specified for this route.');
    }

    // 2. Check if the authenticated user has AT LEAST ONE of these roles
    if (!$request->user()->hasAnyRole($allowedRoles)) {
        abort(404, 'Unauthorized action. Insufficient role privileges.');
    }

    return $next($request);
}

Step 3: Applying the Middleware to Route Groups

With the custom middleware in place, we can now use the expressive pipe syntax directly on our route group:

use Illuminate\Support\Facades\Route;

Route::middleware(['auth', 'role:SuperAdmin|Admin'])
    ->group(function () {
        // These routes are accessible only if the user is EITHER SuperAdmin OR Admin.
        Route::get('/super-admin-settings', function () {
            return "Welcome, Super Admin or Admin!";
        });

        Route::post('/admin/dashboard', function () {
            return "Admin Dashboard access granted.";
        });
    });

Conclusion

By moving the complex logic out of the standard middleware definition and into a custom layer that interacts with your Eloquent relationships, we achieve a powerful, flexible, and clean authorization system. This approach adheres to good software design principles, making our code easier to maintain and scale. For more insights into building robust features in Laravel, always refer back to official documentation and community resources like laravelcompany.com. Mastering these custom middleware patterns is key to unlocking the full potential of a scalable Laravel application.