Autorización y Gates
Autenticación responde "¿quién eres?", pero autorización responde "¿qué puedes hacer?". Laravel proporciona Gates y Policies para controlar qué acciones puede realizar cada usuario en tu aplicación.
Autenticación vs Autorización
Es importante entender la diferencia:
- Autenticación: Verifica la identidad del usuario (login)
- Autorización: Determina qué puede hacer ese usuario
Por ejemplo, en un blog: cualquier usuario autenticado puede crear posts, pero solo el autor de un post puede editarlo o eliminarlo. Eso es autorización.
Gates: La forma más simple
Los Gates son closures que determinan si un usuario
puede realizar una acción. Se definen en el archivo
app/Providers/AppServiceProvider.php:
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Gate simple: ¿puede el usuario editar este post?
Gate::define('update-post', function (User $user, Post $post): bool {
return $user->id === $post->user_id;
});
// Gate para administradores
Gate::define('access-admin', function (User $user): bool {
return $user->is_admin === true;
});
}
}
El primer parámetro del closure siempre es el usuario autenticado. Los siguientes son los recursos que necesitas para tomar la decisión.
Usar Gates en controladores
Una vez definido un Gate, puedes verificarlo de varias formas en tus controladores:
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\View;
class PostController extends Controller
{
public function edit(Post $post): View
{
// Opción 1: Gate::allows() - retorna boolean
if (! Gate::allows('update-post', $post)) {
abort(403, 'No tienes permiso para editar este post.');
}
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post): RedirectResponse
{
// Opción 2: Gate::authorize() - lanza excepción automáticamente
Gate::authorize('update-post', $post);
$post->update($request->validated());
return redirect()->route('posts.show', $post);
}
public function destroy(Post $post): RedirectResponse
{
// Opción 3: Desde el Request
if ($request->user()->cannot('update-post', $post)) {
abort(403);
}
$post->delete();
return redirect()->route('posts.index');
}
}
Consejo: Usa Gate::authorize()
cuando quieras que Laravel maneje el error 403
automáticamente. Usa Gate::allows() cuando
necesites lógica personalizada al denegar.
Métodos can y cannot
El modelo User incluye métodos convenientes para verificar Gates:
<?php
// En un controlador
public function edit(Request $request, Post $post): View
{
// can() retorna true si el usuario puede realizar la acción
if ($request->user()->can('update-post', $post)) {
return view('posts.edit', compact('post'));
}
abort(403);
}
// cannot() es lo opuesto
if ($request->user()->cannot('update-post', $post)) {
abort(403);
}
Gates en vistas Blade
Puedes usar la directiva @can para mostrar
u ocultar elementos según los permisos del usuario:
<article>
<h1>{{ $post->title }}</h1>
<p>{{ $post->content }}</p>
@can('update-post', $post)
<a href="{{ route('posts.edit', $post) }}">Editar</a>
<form method="POST" action="{{ route('posts.destroy', $post) }}">
@csrf
@method('DELETE')
<button type="submit">Eliminar</button>
</form>
@endcan
</article>
También existe @cannot para el caso contrario:
@cannot('update-post', $post)
<p class="text-muted">No tienes permiso para editar este post.</p>
@endcannot
Policies: Gates organizados por modelo
Cuando tienes muchas reglas de autorización para un modelo, es mejor usar Policies. Una Policy es una clase que agrupa toda la lógica de autorización de un modelo.
Crea una Policy con Artisan:
# Crear Policy para el modelo Post
php artisan make:policy PostPolicy --model=Post
Esto crea app/Policies/PostPolicy.php con
métodos predefinidos:
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
/**
* ¿Puede ver la lista de posts?
*/
public function viewAny(User $user): bool
{
return true; // Todos pueden ver la lista
}
/**
* ¿Puede ver este post específico?
*/
public function view(User $user, Post $post): bool
{
// Posts publicados: todos. Borradores: solo el autor
return $post->is_published || $user->id === $post->user_id;
}
/**
* ¿Puede crear posts?
*/
public function create(User $user): bool
{
return true; // Cualquier usuario autenticado
}
/**
* ¿Puede editar este post?
*/
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
/**
* ¿Puede eliminar este post?
*/
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
}
Registrar Policies
Laravel 11+ detecta automáticamente las Policies si sigues
la convención de nombres (PostPolicy para
Post). Si necesitas registrarlas manualmente:
<?php
// app/Providers/AppServiceProvider.php
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Gate::policy(Post::class, PostPolicy::class);
}
}
Usar Policies en controladores
Una vez registrada la Policy, puedes usarla igual que los Gates. Laravel detecta automáticamente qué Policy usar según el modelo:
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\View;
class PostController extends Controller
{
public function index(): View
{
// viewAny no necesita instancia del modelo
Gate::authorize('viewAny', Post::class);
$posts = Post::latest()->paginate(10);
return view('posts.index', compact('posts'));
}
public function show(Post $post): View
{
// view necesita la instancia
Gate::authorize('view', $post);
return view('posts.show', compact('post'));
}
public function edit(Post $post): View
{
Gate::authorize('update', $post);
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post): RedirectResponse
{
Gate::authorize('update', $post);
$post->update($request->validated());
return redirect()->route('posts.show', $post);
}
public function destroy(Post $post): RedirectResponse
{
Gate::authorize('delete', $post);
$post->delete();
return redirect()->route('posts.index');
}
}
Autorización en Form Requests
Los Form Requests tienen un método authorize()
perfecto para verificar permisos antes de validar:
<?php
declare(strict_types=1);
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePostRequest extends FormRequest
{
public function authorize(): bool
{
$post = $this->route('post');
return $this->user()->can('update', $post);
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'content' => ['required', 'string'],
];
}
}
Si authorize() retorna false,
Laravel devuelve automáticamente un error 403.
El método before en Policies
A veces quieres que ciertos usuarios (como administradores)
puedan hacer todo. El método before() se
ejecuta antes que cualquier otro método de la Policy:
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
/**
* Se ejecuta antes de cualquier otro método.
* Si retorna true/false, ese es el resultado final.
* Si retorna null, continúa al método específico.
*/
public function before(User $user, string $ability): ?bool
{
if ($user->is_admin) {
return true; // Administradores pueden todo
}
return null; // Continuar evaluación normal
}
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
}
Cuidado: Si before()
retorna false, ningún otro método se
evaluará. Retorna null para continuar
la evaluación normal.
Autorización para usuarios invitados
Por defecto, Gates y Policies deniegan el acceso si
no hay usuario autenticado. Si quieres permitir que
usuarios invitados pasen la verificación, haz el
parámetro $user nullable:
<?php
// En una Policy
public function view(?User $user, Post $post): bool
{
// Posts públicos: cualquiera puede verlos
if ($post->is_published) {
return true;
}
// Posts privados: solo el autor (si está autenticado)
return $user !== null && $user->id === $post->user_id;
}
// En un Gate
Gate::define('view-post', function (?User $user, Post $post): bool {
return $post->is_published || ($user && $user->id === $post->user_id);
});
Middleware can
Puedes aplicar autorización directamente en las rutas
usando el middleware can:
<?php
use App\Http\Controllers\PostController;
use App\Models\Post;
use Illuminate\Support\Facades\Route;
// Sin modelo (para create)
Route::get('/posts/create', [PostController::class, 'create'])
->middleware('can:create,App\Models\Post');
// Con modelo (usando route model binding)
Route::get('/posts/{post}/edit', [PostController::class, 'edit'])
->middleware('can:update,post');
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware('can:update,post');
Route::delete('/posts/{post}', [PostController::class, 'destroy'])
->middleware('can:delete,post');
Resumen
- La autorización determina qué puede hacer un usuario, no quién es
- Los Gates son closures simples para reglas puntuales
- Las Policies agrupan reglas por modelo (recomendado)
- Usa
Gate::authorize()para lanzar 403 automáticamente - Usa
@canen Blade para mostrar/ocultar elementos - El método
before()permite bypass para administradores - Form Requests pueden incluir lógica de autorización en
authorize() - El middleware
canaplica autorización en rutas
Ejercicios
Ejercicio 1: Gate para administradores
Crea un Gate llamado access-admin que
solo permita acceso a usuarios con el campo
is_admin en true. Luego úsalo
para proteger una ruta /admin/dashboard.
Ver solución
<?php
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Gate::define('access-admin', function (User $user): bool {
return $user->is_admin === true;
});
}
}
<?php
// routes/web.php
use App\Http\Controllers\Admin\DashboardController;
use Illuminate\Support\Facades\Route;
Route::middleware(['auth', 'can:access-admin'])->prefix('admin')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])
->name('admin.dashboard');
});
<?php
// app/Http/Controllers/Admin/DashboardController.php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\View\View;
class DashboardController extends Controller
{
public function index(): View
{
return view('admin.dashboard');
}
}
Ejercicio 2: Policy para comentarios
Crea una CommentPolicy donde: cualquiera
puede ver comentarios, solo usuarios autenticados
pueden crear, y solo el autor puede editar o eliminar
sus propios comentarios.
Ver solución
# Crear la Policy
php artisan make:policy CommentPolicy --model=Comment
<?php
// app/Policies/CommentPolicy.php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Comment;
use App\Models\User;
class CommentPolicy
{
/**
* Cualquiera puede ver la lista de comentarios.
*/
public function viewAny(?User $user): bool
{
return true;
}
/**
* Cualquiera puede ver un comentario específico.
*/
public function view(?User $user, Comment $comment): bool
{
return true;
}
/**
* Solo usuarios autenticados pueden crear comentarios.
*/
public function create(User $user): bool
{
return true;
}
/**
* Solo el autor puede editar su comentario.
*/
public function update(User $user, Comment $comment): bool
{
return $user->id === $comment->user_id;
}
/**
* Solo el autor puede eliminar su comentario.
*/
public function delete(User $user, Comment $comment): bool
{
return $user->id === $comment->user_id;
}
}
<?php
// app/Http/Controllers/CommentController.php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Comment;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\View;
class CommentController extends Controller
{
public function update(Request $request, Comment $comment): RedirectResponse
{
Gate::authorize('update', $comment);
$comment->update($request->validate([
'content' => ['required', 'string', 'max:1000'],
]));
return back()->with('success', 'Comentario actualizado.');
}
public function destroy(Comment $comment): RedirectResponse
{
Gate::authorize('delete', $comment);
$comment->delete();
return back()->with('success', 'Comentario eliminado.');
}
}
Ejercicio 3: Autorización en vistas
Crea una vista que muestre un artículo. El botón "Editar" y "Eliminar" solo deben aparecer si el usuario actual es el autor. Además, si el artículo no está publicado, muestra un badge "Borrador" solo para el autor.
Ver solución
{{-- resources/views/articles/show.blade.php --}}
<x-app-layout>
<article class="max-w-4xl mx-auto p-6">
<header class="mb-6">
<h1 class="text-3xl font-bold">{{ $article->title }}</h1>
<div class="flex items-center gap-4 mt-2 text-gray-600">
<span>Por {{ $article->author->name }}</span>
<span>{{ $article->created_at->format('d/m/Y') }}</span>
@can('update', $article)
@unless($article->is_published)
<span class="px-2 py-1 bg-yellow-100 text-yellow-800 rounded text-sm">
Borrador
</span>
@endunless
@endcan
</div>
</header>
<div class="prose max-w-none">
{!! $article->content !!}
</div>
@can('update', $article)
<footer class="mt-8 pt-6 border-t flex gap-4">
<a
href="{{ route('articles.edit', $article) }}"
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Editar
</a>
<form
method="POST"
action="{{ route('articles.destroy', $article) }}"
onsubmit="return confirm('¿Eliminar este artículo?')"
>
@csrf
@method('DELETE')
<button
type="submit"
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
>
Eliminar
</button>
</form>
</footer>
@endcan
</article>
</x-app-layout>
¿Has encontrado un error o tienes una sugerencia para mejorar esta lección?
Escríbenos¿Te está gustando el curso?
Tenemos cursos premium con proyectos reales, soporte personalizado y certificado.
Descubrir cursos premium