Autorización en Laravel Gate y Police

- Andrés Cruz

In english
Autorización en Laravel Gate y Police

Este material forma parte de mi curso y libro completo; puedes adquirirlos desde el apartado de libros y/o cursos Curso y libro Laravel 11 con Tailwind Vue 3, introducción a Jetstream Livewire e Inerta desde cero - 2024.

En este capítulo vamos a ver una introducción a los Gates (Puerta) en Laravel los cuales permiten manejar la autorización de los usuarios, es decir, para indicar a cuáles partes del sistema pueden ingresar los usuarios en base a reglas impuestas.

Autenticación y autorización

Antes de entrar en detalle, tenemos que tener en cuenta dos conceptos que pueden causar confusión, la autenticación y la autorización.

  • La autenticación hace referencia a cuando el usuario da sus credenciales (usuario/contraseña) a nivel del sistema, es decir, se realiza el login.
  • La autorización hace referencia a que es lo que el usuario puede hacer, es decir, colocar límites; anteriormente vimos cómo hacer este proceso con los roles de administrador y regular, pero, en esta oportunidad estamos empleando un servicio propio de Laravel conocido como Gate.

Puedes imaginar un Gate como una puerta (de allí su nombre) en la cual, si un usuario tiene acceso a ciertas partes del sistema, significa que la puerta está abierta, si no tiene acceso, entonces la puerta permanece cerrada y no podrá acceder a esas partes del sistema.

Aclarado esto, vamos a conocer cómo emplear los Gate en Laravel; un Gate luce de la siguiente manera:

use Illuminate\Support\Facades\Gate;
***
Gate::define('update-post', function ($user, $post) {
    return $user->id == $post->user_id;
});

Aquí podemos ver 3 elementos importantes:

  1. El uso de un Facade: Illuminate\Support\Facades\Gate.
  2. Definir una clave para indicar en pocas palabras qué es lo que va a realizar la operación, en el ejemplo anterior sería "update-post" que sería para actualizar un post, por lo tanto, lo podemos usar en los controladores para editar un formulario (Las funciones de edit() y update()).
  3. El siguiente elemento corresponde a la regla o reglas que quieras imponer, en el ejemplo anterior indicamos que el post debe de pertenecer a un usuario, pero, puedes colocar más como por ejemplo preguntar por el rol del usuario.

Otro punto importante son los argumentos, para poder utilizar la autorización, el usuario debe de estar autenticado y es por eso que el argumento de usuario siempre está presente y es suministrado internamente por Laravel; el resto de los argumentos son completamente personalizables y los que definas depende de las reglas que vayas a establecer.

Cambios iniciales

Para poder hacer algunos ejemplos con los Gates, vamos a necesitar hacer algunos cambios en el proyecto, por ejemplo, agregar una columna de usuario id a la tabla post; para ello, creamos una migración:

$ php artisan make:migration add_user_id_to_posts_table

Definimos la nueva columna:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->foreignId('user_id')->constrained()
                ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropColumn('user_id');
        });
    }
};

Y ejecutamos el comando de migración:

$ php artisan migrate:fresh

Este comando borrar todas las tablas y las vuelva a generar, esto lo hacemos ya que, existen posts en la base de datos y no podemos agregar una nueva columna de tipo foránea (no nula) a posts existentes.

Aplicamos los cambios en el modelo:

app\Models\Post.php

class Post extends Model
{
    use HasFactory;

    protected $fillable = [***, 'user_id'];
    ***
}

Y en el factory, agregamos el campo de usuario id:

database\factories\PostFactory.php

class PostFactory extends Factory
{
    public function definition(): array
    {
        // Post::truncate();
        $name = fake()->sentence;
        return [
            'title' => $name,
            'slug' => str($name)->slug(),
            'content' => $this->faker->paragraphs(20, true),
            'description' => $this->faker->paragraphs(4, true),
            'category_id' => $this->faker->randomElement([1, 2, 3]),
            'user_id' => $this->faker->randomElement([1, 2]),
            'posted' => $this->faker->randomElement(['yes', 'not']),
            'image' => $this->faker->imageUrl()
        ];
    }
}

Con esto, tenemos listo los cambios iniciales para poder crear nuestra primera regla mediante los Gate.

Gate define y allow, métodos claves

Hay dos métodos muy importantes en los Gate, el primero es el de define() para definir el Gate con las reglas tal cual vimos antes:

app\Providers\AuthServiceProvider.php

public function boot(): void
{
    ***
    Gate::define('update-post', function ($user, $post) {
        return $user->id == $post->user_id;
    });

}

Como puedes ver en el código anterior, los Gate están definidos en AuthServiceProvider.php.

Y para poder usar el Gate anterior, usamos la función de allows(), ya que el Gate anterior es para prevenir que los usuarios no pueden modificar posts de otros usuarios, lo usamos en los métodos de edición:

app\Http\Controllers\Dashboard\PostController.php

use Illuminate\Support\Facades\Gate;
***
class PostController extends Controller
{
    public function edit(Post $post): View
    {
        if (!Gate::allows('update-post', $post)) {
            return abort(403);
        }
        ***
    }

    public function update(PutRequest $request, Post $post): RedirectResponse
    {
        if (!Gate::allows('update-post', $post)) {
            return abort(403);
        }
        ***
    }
}

Y verás que, desde el Dashboard cuando intentes modificar un post que no pertenece a un usuario, aparece un error 403 que por supuesto puedes personalizar con una redirección o cualquier otra operación.

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.