Autorización en Laravel con Gates y Policies
La autorización en Laravel es una de esas funciones que separa un proyecto básico de uno verdaderamente profesional. En este artículo te voy a explicar, con ejemplos reales y código funcional, cómo usar Gates y Policies para controlar el acceso de los usuarios en tu aplicación usando claro está, el sistema de Localizaciones y Traducciones en Laravel para transmitir correctamente el mensaje.
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.
Introducción a la autorización en Laravel
Diferencia entre autenticación y autorización
Antes de tocar código, hay que tener claro que autenticación y autorización no son lo mismo.
- Autenticació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.
- Autorización: 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.
En mi caso, lo entendí mejor al imaginar un Gate como una puerta. Si el usuario tiene permiso, la puerta se abre; si no, permanece cerrada. Laravel justamente implementa ese concepto: un Gate decide quién puede pasar y quién no.
¿Por qué usar Gates y Policies en lugar de roles tradicionales o Spatie?
Usar roles como “admin” o “usuario regular” puede funcionar al inicio, pero no escala bien. Los Gates y Policies permiten definir reglas más precisas y centralizadas, facilitando el mantenimiento y la seguridad del código.
Cuando se trata de controlar el acceso a diferentes partes de una aplicación web, los Gate en Laravel son una herramienta que podemos emplear de manera directa sin necesidad de instalar paquetes adicionales como Spatie. En este artículo, exploraremos qué son los Gate, cómo funcionan y cómo puedes aprovecharlos en tu proyecto.
Qué son los Gates en Laravel
Los Gate son una característica de autorización en Laravel que te permite definir reglas para determinar si un usuario tiene permiso para realizar una acción específica. Estas reglas se aplican a nivel de aplicación y pueden ser utilizadas para proteger rutas, acciones de controladores y cualquier otra parte de tu aplicación.
Los Gate en Laravel son una herramienta esencial para garantizar la seguridad y la autorización en tu aplicación. Aprender a utilizarlos correctamente te permitirá controlar quién puede acceder a qué partes de tu sistema.
Gate es lo mismo que Permiso
Una de las mayores confusiones, en mi opinión, es el término "gate". Creo que los desarrolladores habrían evitado mucha confusión si se les llamara como son.
Las puertas son permisos, llamados simplemente con otra palabra.
¿Cuáles son las acciones típicas que debemos realizar con permisos?
- Define el permiso, ej. “administrar_usuarios”
- Verifique el permiso en el front-end, ej. mostrar/ocultar el botón
- Verifique el permiso en el back-end, ej. no puedo/no puedo actualizar los datos
Así que sí, reemplaza la palabra "permiso" por "gate" y lo entenderás todo.
Definición y funcionamiento básico
Los Gates son funciones que determinan si un usuario puede ejecutar una acción específica. Se definen normalmente en el archivo:
app/Providers/AuthServiceProvider.phpCómo crear un Gate en AuthServiceProvider
Por ejemplo, este Gate permite actualizar solo los posts creados por el mismo usuario:
use Illuminate\Support\Facades\Gate;
Gate::define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});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.
Aquí podemos ver 3 elementos importantes:
- El uso de un Facade: Illuminate\Support\Facades\Gate.
- 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()).
- 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.
Varias formas de verificar el permiso
Otra fuente de confusión es cómo/dónde revisar el Gate. Es tan flexible que puedes encontrar ejemplos muy diferentes. Repasémoslos:
Opción 1. Rutas: middleware('can:<YourGate>')
Este es el ejemplo de arriba. Directamente en la ruta/grupo, podrá asignar el middleware:
Route::post('users', [UserController::class, 'store'])
->middleware('can:create_users');Opción 2. Controlador: can () / cannot ()
En las primeras líneas del método Controller podemos ver algo como esto, con métodos can() o can(), idénticos a las directivas Blade:
public function store(Request $request)
{
if (!$request->user()->can('create_users'))
abort(403);
}
}El opuesto al anterior:
public function store(Request $request)
{
if ($request->user()->cannot('create_users'))
abort(403);
}
}O, si no tienes una variable $request, puedes usar el asistente auth():
public function create()
{
if (!auth()->user()->can('create_users'))
abort(403);
}
}Opción 3. Gate::allows() o Gate::denies()
Otra forma es utilizar una fachada de puerta:
public function store(Request $request)
{
if (!Gate::allows('create_users')) {
abort(403);
}
}O el negado:
public function store(Request $request)
{
if (Gate::denies('create_users')) {
abort(403);
}
}O, una forma más corta es usando helpers:
public function store(Request $request)
{
abort_if(Gate::denies('create_users'), 403);
}Opción 4. Controller: authorize()
Una opción aún más corta, y mi favorita, es usar Authorize() en Controladores. En caso de falla, devolvería una página 403 automáticamente.
public function store(Request $request)
{
$this->authorize('create_users');
}Opción 5. Clase Form Request
He notado que muchos desarrolladores generan clases de Solicitud de formulario solo para definir las reglas de validación, ignorando por completo el primer método de esa clase, que es autorizar().
También puedes usarlo para revisar los Gates. De esta manera, logra una separación de preocupaciones, lo cual es una buena práctica para código sólido, de modo que el Controlador no se encarga de la validación, porque se realiza en su clase dedicada de Solicitud de formulario.
public function store(StoreUserRequest $request)
{
// No check is needed in the Controller method
}
class StoreProjectRequest extends FormRequest
{
public function authorize()
{
return Gate::allows('create_users');
}
public function rules()
{
return [
// ...
];
}
}Ejemplo práctico: restringir la edición de un Post
Índice de contenido
- Introducción a la autorización en Laravel
- ¿Por qué usar Gates y Policies en lugar de roles tradicionales o Spatie?
- Qué son los Gates en Laravel
- Gate es lo mismo que Permiso
- Definición y funcionamiento básico
- Varias formas de verificar el permiso
-
Opción 1. Rutas: middleware('can:
') - Opción 2. Controlador: can () / cannot ()
- Opción 3. Gate::allows() o Gate::denies()
- Opción 4. Controller: authorize()
- Opción 5. Clase Form Request
- Ejemplo práctico: restringir la edición de un Post
- Gate define y allow, métodos claves
- Qué son las Policies en Laravel
- Política: conjunto de permisos basado en modelos
- Usar Gates y Policies en controladores y vistas
- ⚠️ Errores comunes y cómo evitarlos
- Error 403 al aplicar un Gate
- Policies no registradas o mal vinculadas
- Mejores prácticas y consejos de experiencia
- Rol: Conjunto universal de permisos
- Paquetes para administrar roles/permisos
- ¿Qué pasa con los Guards?
- Conclusión
- ❓ Preguntas frecuentes
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_tableData de prueba con el PostFactory:
'user_id' => $this->faker->randomElement([1, 2]),Esto permitió vincular correctamente los posts con usuarios y probar el Gate sin errores.
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:freshEste 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.
Qué son las Policies en Laravel
Cuándo conviene usar una Policy
Las Policies son clases dedicadas a manejar la autorización de un modelo completo.
Si el Gate es una “puerta específica”, una Policy es “el guardia de todo el edificio”.
Conviene usar Policies cuando:
- Tienes muchas reglas para un mismo modelo.
- Quieres mantener el código ordenado.
- Deseas aprovechar métodos automáticos como viewAny, update, delete, etc.
- Crear una Policy con Artisan paso a paso
Laravel lo hace sencillo:
$ php artisan make:policy PostPolicy --model=PostEsto crea un archivo en app/Policies/PostPolicy.php con métodos base.
Registrar y aplicar una Policy a un modelo
Edita tu AuthServiceProvider y registra la clase:
protected $policies = [
Post::class => PostPolicy::class,
];Ahora Laravel sabe qué Policy aplicar para ese modelo.
Ejemplo completo de autorización con Policy
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}Y en el controlador:
$this->authorize('update', $post);Desde que empecé a usar Policies, mi código se volvió más limpio y mucho más fácil de mantener.
Política: conjunto de permisos basado en modelos
Si sus permisos se pueden asignar a un modelo Eloquent, en un controlador CRUD típico, puede crear una clase de Política alrededor de ellos.
Si ejecutamos este comando:
php artisan make:policy ProductPolicy --model=ProductGenerará el archivo app/Policies/UserPolicy.php, con los métodos predeterminados que tienen un comentario para explicar su propósito:
use App\Models\Product;
use App\Models\User;
class ProductPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user)
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Product $product)
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user)
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Product $product)
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Product $product)
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Product $product)
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Product $product)
{
//
}
}En cada uno de esos métodos, se define la condición para la devolución de true/false. Entonces, si seguimos los mismos ejemplos que Gates antes, podemos hacer esto:
class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}
}Luego, puede verificar la Política de una manera muy similar a la de Gates:
public function store(Request $request)
{
$this->authorize('create', Product::class);
}Entonces, especifica el nombre del método y el nombre de clase de la Política.
En otras palabras, las Políticas son simplemente otra forma de agrupar los permisos, en lugar de Puertas. Si sus acciones giran principalmente en torno a CRUD de modelos, entonces las políticas probablemente sean una opción más conveniente y mejor estructurada que Gates.
Usar Gates y Policies en controladores y vistas
Uso de Gate::allows() y can() en controladores
En controladores, puedes autorizar así:
if (Gate::denies('update-post', $post)) {
abort(403);
}O usar el helper más legible:
if ($user->can('update', $post)) {
// acción permitida
}Autorización directa en Blade con @can y @cannot
En las vistas Blade, Laravel permite simplificarlo con directivas:
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Editar</a>
@endcanIntegración con middleware para proteger rutas
También puedes usar middleware en las rutas:
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware('can:update,post');Esto evita que usuarios no autorizados siquiera lleguen al controlador.
⚠️ Errores comunes y cómo evitarlos
Error 403 al aplicar un Gate
A menudo no es un fallo del Gate, sino que el usuario no cumple las condiciones. Asegúrate de que $user y $post estén correctamente pasados.
Problemas de migración y claves foráneas
Cuando añadí user_id a mi tabla, tuve que ejecutar:
$ php artisan migrate:freshPara regenerar la base de datos sin conflictos de claves.
Policies no registradas o mal vinculadas
Si tu Policy no se ejecuta, revisa que esté registrada en AuthServiceProvider y que uses el mismo modelo exacto.
Mejores prácticas y consejos de experiencia
- Mantén las reglas centralizadas en Policies
- Evita dispersar la lógica por controladores. Centraliza todo en tus Policies.
- Nombra Gates y Policies de forma coherente
- Usa nombres cortos y descriptivos: update-post, delete-comment, publish-article.
- Usa testing para verificar la autorización
- Laravel permite testear Gates fácilmente:
- $this->assertTrue(Gate::forUser($user)->allows('update-post', $post));
- Combina roles y Policies para proyectos complejos
- En sistemas grandes, suelo combinar roles con Policies para obtener un control de acceso más granular.
Rol: Conjunto universal de permisos
Analicemos otra confusión: en la documentación de Laravel, no encontrará ninguna sección sobre Roles de usuario. La razón es simple: el término "roles" está inventado artificialmente para agrupar el permiso bajo algún tipo de nombre, como "administrador" o "editor".
Desde el punto de vista del marco, no hay "roles", sólo gates/políticas que puede agrupar de la forma que desee.
En otras palabras, un rol es una entidad FUERA del marco de Laravel, por lo que debemos construir la estructura de roles nosotros mismos. Puede ser parte de la confusión general de autenticación, pero tiene mucho sentido porque debemos controlar cómo se definen los roles:
- ¿Es un rol o múltiples roles?
- ¿Puede un usuario tener un rol o múltiples roles?
- ¿Quién puede gestionar los roles en el sistema?
etc.
Entonces, la funcionalidad Role es otra capa de su aplicación Laravel. Aquí es donde llegamos a los paquetes de Laravel que pueden ayudar. Pero también podemos crear los roles sin ningún paquete:
- Cree una tabla de base de datos de "roles" y un modelo de roles
- Agregue una relación de Usuario a Rol: uno a muchos o muchos a muchos
- Defina los roles predeterminados y asignarlos a los usuarios existentes
- Asignar un rol predeterminado en el registro
- Cambie Gate/políticas para verificar el rol en su lugar
Entonces, en lugar de:
class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}
}Hacemos:
class ProductPolicy
{
public function create(User $user)
{
return $user->role_id == Role::ADMIN;
}
}Nuevamente, aquí tienes algunas opciones para verificar los roles. En el ejemplo anterior, asumimos que hay una relación de pertenencia entre Usuario y Rol, y también hay constantes en el modelo de rol como ADMIN = 1, como EDITOR = 2, solo para evitar consultar demasiado la base de datos.
Pero si prefieres ser flexible, puedes consultar la base de datos cada vez:
class ProductPolicy
{
public function create(User $user)
{
return $user->role->name == 'Administrator';
}Paquetes para administrar roles/permisos
Los paquetes más populares para esto son Spatie Laravel Permission y Bouncer, tengo un artículo largo aparte sobre ellos. El artículo es muy antiguo, pero los líderes del mercado siguen siendo los mismos debido a su estabilidad.
Lo que hacen esos paquetes es ayudarle a abstraer la gestión de permisos en un lenguaje amigable para los humanos, con métodos que pueda recordar y usar fácilmente.
Mire esta hermosa sintaxis del permiso Spatie:
$user->givePermissionTo('edit articles');
$user->assignRole('writer');
$role->givePermissionTo('edit articles');
$user->can('edit articles');Bouncer es quizás un poco menos intuitivo pero sigue siendo muy bueno:
Bouncer::allow($user)->to('create', Post::class);
Bouncer::allow('admin')->to('ban-users');
Bouncer::assign('admin')->to($user);Entonces, estos paquetes son la "capa" final de autenticación/autorización que cubrimos aquí en este artículo. Espero que ahora tengas una idea completa y puedas elegir qué estrategia usar.
¿Qué pasa con los Guards?
Ah, esos. Causan tanta confusión a lo largo de los años. Muchos desarrolladores pensaron que los guardias son roles y comenzaron a crear tablas de base de datos separadas como "administradores" y luego asignándolas como Guards. En parte, porque en la documentación puede encontrar fragmentos de código como Auth::guard('admin')->attempt($credentials))
Incluso envié una solicitud de extracción a los documentos con una advertencia para evitar este malentendido.
En la documentación oficial, puede encontrar este párrafo:
En esencia, las instalaciones de autenticación de Laravel están compuestas por "Guards" y "proveedores". Los guardias definen cómo se autentican los usuarios para cada solicitud. Por ejemplo, Laravel viene con un protector de sesión que mantiene el estado mediante el almacenamiento de sesión y las cookies.
Entonces, los Guards son un concepto más global que los roles. Un ejemplo de guardia es "sesión"; más adelante en la documentación, podrá ver un ejemplo de guardia JWT. En otras palabras, una guardia es un mecanismo de autenticación completo y, para la mayoría de los proyectos de Laravel, nunca necesitarás cambiar el Guard ni siquiera saber cómo funcionan. Los Guards están fuera de este tema de roles/permisos.
Conclusión
Laravel ofrece un sistema de autorización robusto, seguro y elegante.
Los Gates te sirven para reglas puntuales; las Policies, para modelos completos.
Cuando combinas ambos, consigues un flujo limpio, centralizado y fácil de mantener.
En mi experiencia, dominar estas herramientas te da una enorme ventaja en cualquier proyecto real con Laravel.
❓ Preguntas frecuentes
¿Qué diferencia hay entre un Gate y una Policy en Laravel?
Un Gate maneja una acción puntual; una Policy, las acciones de un modelo completo.
¿Dónde se definen los Gates en Laravel?
Normalmente en app/Providers/AuthServiceProvider.php.
¿Cómo manejar un error 403?
Verifica que el usuario esté autenticado y cumpla las condiciones del Gate o Policy.
Siguiente paso, el sistema de dominios/subdominios en Laravel
Acepto recibir anuncios de interes sobre este Blog.
Los gates son una característica de autorización que le permiten definir políticas para controlar el acceso a ciertas partes de su aplicación.