banner tutorial roles y permisos con Spatie/laravel-permission

Manejar permisos y roles para nuestros usuarios dentro de una aplicación nos da una gran flexibilidad a la hora de tomar decisiones sobre a qué grupo de usuarios debemos mostrar algún tipo de contenido y a quienes debemos ocultárselos. Si tienes algo de experiencia o tiempo en este mundo del desarrollo quizás ya sepas algo sobre ACL, sino vamos a explicar un poco de qué se trata antes de trabajar con el componente:

ACL (Access Control List)

Para ser muy breve, Access Control List no es más que un concepto de seguridad informática el cual es usado para separar los privilegios o permisos de nuestra aplicación. Podemos trabajar con un sistema de roles o simplemente asignando a los usuarios cuáles van a ser sus permisos dentro de nuestra aplicación.

¿Qué es spatie/laravel-permission?

Es un paquete para Laravel 5.x desarrollado por la comunidad de Spatie. Este paquete nos permite asociar a nuestros usuarios roles y permisos que serán guardados en nuestra base de datos sin tener que crear las migraciones manualmente, sino que ya el paquete nos las trae listas, además nos ofrece un par de modelos para los roles y permisos con una serie de métodos que nos garantizan mucha simplicidad.

A continuación te mostraré un pequeño ejemplo:

$role->givePermissionTo('edit articles');

Con esta simple línea de código estaríamos agregando un nuevo permiso a un rol existente.

Instalación del paquete

Para instalar el paquete debemos requerirlo en nuestra aplicación mediante Composer y esto lo podemos hacer con el siguiente comando:

composer require spatie/laravel-permission

Una vez ejecutado el comando debemos esperar a que todas las dependencias sean cargadas y cuando termine la instalación debemos proceder a registrar el Service Provider en nuestra aplicación, para eso abrimos el archivo config/app.php y colocamos la siguiente línea en el arreglo providers:

/*
* Package Service Providers...
*/

Spatie\Permission\PermissionServiceProvider::class,

Con esto sólo nos queda un par de pasos donde vamos a extraer un archivo de configuración y un archivo de migraciones que pertenecen al paquete que estamos instalando y se encuentran en el directorio vendor para integrarlos a nuestra aplicación.

Para extraer el archivo de configuración ejecutamos el siguiente comando, que guardará el archivo en el directorio app/config:

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"

Para extraer el archivo de migraciones, ejecutamos el siguiente comando que almacenará el archivo en el directorio database/migrations:

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"

Ahora, el archivo laravel-permission.php ubicado en nuestra carpeta config que contiene información sobre quienes serán los modelos para Role y Permission, además contiene el nombre de las tablas que se usarán para los roles y permisos junto a las tablas de las relaciones. De igual forma, tendremos un nuevo archivo de migración que contiene la creación de todas las tablas necesarias.

Vamos a comenzar a trabajar con nuestros modelos. Pero antes debemos configurar el archivo .env ubicado en la raíz del proyecto y colocar las credenciales de acceso para tu base de datos:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

Ahora que ya tenemos conexión a nuestra base de datos, podemos proceder a ejecutar las migraciones y para eso escribimos el siguiente comando en nuestra consola o terminal:

php artisan migrate

Con esto podemos notar que tenemos 5 tablas generadas por el paquete junto a las demás tablas y son las siguientes:

  • permissions
  • roles
  • roles_has_permissions
  • users_has_permissions
  • user_has_roles

Uso del paquete

Para demostrar el uso del paquete trabajaremos con un ejemplo donde vamos a crear un rol y un permiso para eliminar notas, teniendo en cuenta que tenemos una instalación de Laravel 5.x y con el paquete ya instalado entonces vamos a proceder a ejecutar el siguiente comando:

php artisan make:auth

Con este comando estamos generando el sistema de autentificación que nos trae Laravel por defecto, contiene un registro, login y recuperación de clave, puedes encontrar mas información sobre este comando en el siguiente enlace Registro, login y recuperación de clave con el comando make:auth en Laravel 5.3.

Ahora vamos a crear un nuevo modelo llamado Note junto a su migración con el siguiente comando:

php artisan make:model Note -m

Usamos la opción -m para que se cree también la migración del modelo. Vamos a rellenar la migración escribiendo el método up de la siguiente forma:

Schema::create('notes', function (Blueprint $table) {
    $table->increments('id');
    $table->string('title');
    $table->text('content');
    $table->timestamps();
});

Luego de rellenar nuestra migración debemos ejecutarlas en nuestra consola o terminal con el comando:

php artisan migrate

Debemos modificar nuestro modelo User para agregar el trait del paquete llamado HasRoles, el cual es el encargado de ofrecernos una serie de métodos para trabajar con roles y permisos que estarán asociados a nuestro modelo. No olvides agregar el namespace antes de usar el trait. Además, especificamos que campos se pueden rellenar de forma masiva:

use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;

    protected $fillable = ['title', 'content'];
}

Para nuestro ejemplo solo vamos a permitir que los usuarios con el rol Administrator puedan eliminar las notas, por supuesto que debemos indicarle a nuestro rol Administrator que tendrá permisos para eliminar las notas. Para crear el rol y el permiso utilizaremos la consola interactiva Tinker, podemos hacerlo de la siguiente forma:

php artisan tinker

Ahora que estamos dentro de la consola creamos el rol de la siguiente forma:

use Spatie\Permission\Models\Role;

$role = Role::create(['name' => 'Administrator']);

Puedes crear un formulario para esto, pero como estamos trabajando con un ejemplo estamos intentando ser lo más breve posible. Si deseas crear formularios puedes hacerlo siguiendo el tutorial Creando formularios con el paquete Styde/Html.

Continuando con nuestro ejemplo, podemos observar que se ha guardado el rol en una variable para usarlo más adelante. Ahora seguimos con la creación del permiso:

use Spatie\Permission\Models\Permission;

Permission::create(['name' => 'destroy_notes']);

Necesitamos decirle a nuestra aplicación que el rol Administrator puede eliminar notas. Así recordando que tenemos al rol guardado en la variable $role, le asignaremos el permiso destroy_notes:

// La variable $role contiene el rol Administrator
$role->givePermissionTo('destroy_notes');

Ahora podemos verificarlo usando el método hasPermissionTo que recibe como parámetro el permiso a evaluar y devuelve un valor booleano de la siguiente forma:

$role->hasPermissionTo('destroy_notes');

Vamos a crear un nuevo usuario, le asignaremos el rol de Administrator y todo esto lo guardaremos en un variable llamada $user para luego asignarle el rol.

// Creación del usuario
$user = User::create([
    'name' => 'Styde',
    'email' => '[email protected]',
    'password' => bcrypt('secret')
]);

// Asignación del rol
$user->assignRole('Administrator');

Tenemos hasta ahora un usuario que tiene un rol y éste tiene asignado el permiso. Ahora crearemos una vista donde tendremos una lista de notas y solo le mostraremos el enlace para eliminar la nota al usuario autentificado que debe tener un rol con el permiso destroy_notes. Vamos directamente a crear lo necesario para esto comenzando por un controlador:

php artisan make:controller NotesController

Agregaremos un método index de esta forma:

public function index()
{
    $notes = \App\Note::all();

    return view('notes.index', compact('notes'));
}

Vamos a crear una nueva carpeta en resources/views llamada notes y dentro de ella crearemos una vista llamada index.blade.php con el siguiente contenido:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Listado de notas</title>
</head>
<body>
    <table>
        <thead>
            <th>ID</th>
            <th>Título</th>
            <th>Acción</th>
        </thead>
        <tbody>
            @foreach ($notes as $note)
                <tr>
                    <td>{{ $note->id }}</td>
                    <td>{{ $note->title }}</td>
                    <td>
                        @can('destroy_notes')
                            <a href="{{ route('notes.destroy', $note->id) }}">Eliminar nota</a>
                        @else
                            Usted no puede eliminar esta nota
                        @endcan
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>
</body>
</html>

Se que has notado la directiva @can y es nativa de Laravel además puedes encontrar más información sobre ella en el tutorial Uso del componente de autorización en Blade, con la directiva @can del Curso introductorio de Laravel 5.1. Esta directiva nos ayudará a verificar si el rol del usuario autentificado contiene un permiso llamado destroy_notes.

Debemos crear una ruta de tipo get llamada notes.destroy la cual recibirá el id de la nota que queremos eliminar (no crearemos la ruta de tipo delete para simplificar el ejemplo). Creamos las rutas en nuestro archivo routes/web.php

Route::get('notes', 'NotesController@index');
Route::get('notes/{id}/destroy', 'NotesController@destroy')->name('notes.destroy');

Seguidamente creamos el método necesario en nuestro controlador NotesController de la siguiente forma:

public function destroy($id)
{
    App\Note::findOrFail($id)->delete();

    return redirect()->back();
}

Nuestro sistema de roles y permisos para eliminar las notas funciona correctamente y esto lo podemos validar creando un nuevo usuario sin asignarle algún rol o permiso:

App\User::create([
    'name' => 'Joe',
    'email' => '[email protected]',
    'password' => bcrypt('secret')
]);

Vamos también a crear una nueva nota de la siguiente forma:

App\Note::create([
    'title' => 'Titulo de la nota',
    'content' => 'Contenido de la nueva nota'
]);

(Puedes crear el usuario y la nota usando Tinker o a través de un Seeder).

Si nos logueamos con este usuario que acabamos de crear en la dirección http://localhost:8000/login y luego nos dirigimos hacia http://localhost:8000/notes podemos observar un mensaje que dice «Usted no puede eliminar esta nota» ¡Pero podemos acceder a la ruta para eliminarla sin tener el permiso necesario con la dirección http://localhost:8000/notes/1/destroy!

Para solucionar este problema debemos crear un middleware, registrarlo en el kernel de las rutas y seguidamente creamos un grupo de rutas donde especificaremos que usaremos el middleware.

Vamos a crearlo, ejecutando:

php artisan make:middleware PermissionMiddleware

Debemos dirigirnos a app/Http/Middleware/PermissionMiddleware.php y editar el método handle con nuestra lógica de la siguiente forma:

public function handle($request, Closure $next, $permission)
{
    if (Auth::guest()) {
        return redirect('/login');
    }

    if (! $request->user()->can($permission)) {
       abort(403);
    }

    return $next($request);
}

Con esto validamos que el usuario esté autentificado y a su vez validamos que el usuario autenticado tiene el permiso que le pasemos por parámetro.

Seguidamente debemos registrar este middleware en app/Http/Kernel.php específicamente en el arreglo del atributo $routeMiddleware añadiendo:

protected $routeMiddleware = [
        //
        'permission' => \App\Http\Middleware\PermissionMiddleware::class,
];

Con esto podemos usar este middleware en nuestras rutas, para el ejemplo lo usaremos dentro de un grupo de rutas de la siguiente forma:

Route::group(['middleware' => ['permission:destroy_notes']], function () {
    Route::get('notes/{id}/destroy', 'NotesController@destroy')->name('notes.destroy');
});

Con esto nuestra ruta ya está protegida y en el caso de intentar acceder sin tener el permiso necesario el usuario obtendrá un error 403, gracias al middleware.

Puedes crear una vista para manejar este error siguiendo el tutorial de Manejo de errores y excepciones.

Por último, no podemos dejar atrás un par de métodos útiles los cuales nos permitirán validar si un rol ya tiene un permiso y eliminar un permiso de un rol, estos son los métodos:

$role->hasPermissionTo('destroy_notes'); // Validamos que el rol contenga el permiso
$role->revokePermissionTo('destroy_notes'); // Eliminamos el permiso del rol

Si quieres saber mucho más del paquete y conocer todas sus características te invito a revisar  el repositorio en GitHub: https://github.com/spatie/laravel-permission

Conclusión

Manejar permisos y roles en una aplicación nos ofrece mucha flexibilidad al tener que manipular contenido que necesitamos ocultar. Esperamos que sea de tu utilidad este artículo y por favor no olvides compartirlo en las redes sociales.

Material relacionado

Únete a nuestra comunidad en Discord y comparte con los usuarios y autores de Styde, 100% gratis.

Únete hoy

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.