Spatie Laravel Permission 8.4.0 simplifica los grupos de rutas
Aplica roles y permisos a grupos de rutas de Laravel sin repetir middleware en cada endpoint.
Proteger un panel no debería llenar el archivo de rutas
Cuando monto un panel interno en Laravel, sea para una aplicación de trabajo o para gestionar servicios del homelab, casi siempre hay varias rutas con la misma regla de acceso. Antes era fácil acabar aplicando el mismo middleware a cada endpoint o escribir grupos con cadenas de middleware que no explicaban demasiado bien la intención. Funciona, pero el archivo crece y es más fácil dejar una ruta nueva fuera de la protección por error.
Spatie Laravel Permission 8.4.0 permite usar las macros role, permission y roleOrPermission directamente sobre grupos de rutas. La ventaja no está en tener un sistema nuevo de autorización, sino en expresar la regla donde se define el bloque de rutas. Si una sección completa requiere un rol o permiso concreto, la restricción queda pegada al grupo que organiza esa sección.
El caso práctico de un panel interno
Imagina un área /ops desde la que consultas el estado de máquinas, ejecutas tareas de mantenimiento o revisas copias de seguridad. No quiero decidir ruta a ruta quién puede ver cada pantalla si todas pertenecen al mismo panel operativo. Prefiero declarar la autenticación, el prefijo y la autorización una vez, y dejar dentro únicamente las rutas que forman parte de esa zona.
En la versión 8.4.0 puedo encadenar middleware(), prefix() y la macro de autorización antes de cerrar el grupo. El paquete acepta para estas macros los mismos tipos de entrada que en rutas individuales: un string, un array o un enum respaldado. Para un grupo con dos roles válidos, el código puede quedar así:
use Illuminate\Support\Facades\Route;
Route::middleware(['auth'])
->prefix('ops')
->role(['admin', 'operator'])
->group(function () {
Route::get('/', OpsDashboardController::class)->name('ops.dashboard');
Route::get('/backups', BackupController::class)->name('ops.backups');
Route::post('/maintenance', MaintenanceController::class)->name('ops.maintenance');
});El array no significa que el usuario deba tener ambos roles. El paquete convierte los valores en una comprobación alternativa, igual que cuando se separan varios nombres con | en el middleware tradicional. Si quiero proteger una sección por capacidades y no por cargos, puedo usar permission(); si me sirve cualquiera de los dos conceptos, roleOrPermission() cubre ese caso.
Roles para zonas y permisos para acciones
Yo intentaría no convertir cada pantalla en un rol nuevo. Un rol suele describir una función estable, como operator o admin, mientras que un permiso expresa una capacidad más concreta, como view backups o run maintenance. Para cerrar una zona administrativa entera, un rol puede ser una regla razonable y fácil de leer.
Pero cuando una acción tiene impacto, prefiero una comprobación de permiso. Por ejemplo, una ruta de lectura de métricas puede pertenecer al grupo de operadores, mientras que la ruta que lanza una limpieza debería pedir una capacidad específica. Laravel Permission sigue permitiendo combinar middleware en rutas individuales dentro del grupo cuando hace falta bajar a ese nivel de detalle.
Route::middleware(['auth'])
->prefix('ops')
->role('operator')
->group(function () {
Route::get('/metrics', MetricsController::class);
Route::post('/cleanup', CleanupController::class)
->middleware('permission:run maintenance');
});Así el grupo define el perímetro habitual y la ruta sensible añade una condición propia. Es un patrón especialmente útil en paneles que empiezan pequeños y luego incorporan operaciones que no deberían estar disponibles para todo el mundo que puede entrar. También evita que la organización de las URLs dicte por sí sola el modelo de permisos.
La letra pequeña está en el orden
Hay un detalle importante en los grupos: el orden de las llamadas importa. La nota de la versión advierte de que Laravel puede sustituir el middleware del grupo según cómo se encadene la configuración. Por eso conviene llamar a middleware() antes de role(), permission() o roleOrPermission().
Si necesito añadir middleware después de definir la autorización, usaría grupos anidados en lugar de confiar en que Laravel acumule la configuración. En rutas individuales, el comportamiento es distinto y el middleware se añade a la ruta. No asumiría que una cadena que funciona en una ruta aislada tendrá exactamente el mismo efecto cuando se mueve a un grupo.
Lo que esta mejora no resuelve
Esta versión no crea roles, permisos ni relaciones entre usuarios y capacidades. El modelo autenticable debe seguir usando el trait HasRoles, y las reglas que nombres en las rutas deben existir y estar asignadas según el diseño de tu aplicación. Tampoco sustituye políticas de Laravel cuando la decisión depende del recurso concreto, por ejemplo, decidir si una persona puede editar precisamente este servidor, esta factura o este proyecto.
También hay que tener cuidado con los nombres incluidos en un array o en un string separado por barras verticales: representan alternativas, no una condición de “debe tener todos”. Si necesito exigir varias condiciones, las expresaría como middleware separados o mediante una política. Y, como siempre, proteger una ruta no basta para asegurar un panel si la acción termina ejecutando comandos, tocando infraestructura o exponiendo secretos sin validaciones adicionales.
El cambio de 8.4.0 es pequeño, pero encaja bien con una idea simple: la seguridad de una sección debería leerse en la definición de esa sección. Para paneles internos, herramientas de administración y servicios propios del homelab, quitar repetición del archivo de rutas también reduce oportunidades de equivocarse.