La autenticación confirma «quién es» un usuario. El inicio de sesión es su ejemplo más habitual.
La autorización determina «qué puede hacer» ese usuario. Por ejemplo, permitir que solo edite sus propios posts o que únicamente los administradores modifiquen los ajustes.Una vez que hayas implementado el inicio de sesión mediante autenticación, el siguiente paso es la autorización.
Las funciones de autorización de Laravel ofrecen dos enfoques: gates y policies. Cuál usar depende de tu caso de uso.
Criterios para decidir
Escenario
Recomendado
Comprobaciones sencillas que no están asociadas a un modelo concreto
Gates
Gestión de permisos CRUD sobre un modelo
Policies
Control de acceso al panel de administración
Gates
Permisos de creación, edición y borrado de posts de un blog
Los gates se definen en el método boot de App\Providers\AppServiceProvider con Gate::define().
// app/Providers/AppServiceProvider.phpuse App\Models\Post;use App\Models\User;use Illuminate\Support\Facades\Gate;public function boot(): void{ Gate::define('update-post', function (User $user, Post $post) { return $user->id === $post->user_id; });}
La closure de un gate recibe siempre como primer argumento el usuario autenticado actualmente. A partir del segundo argumento puedes pasar el modelo objetivo u otros datos.
Para comprobar permisos desde un controlador utiliza Gate::allows() o Gate::denies().
use Illuminate\Support\Facades\Gate;public function update(Request $request, Post $post): RedirectResponse{ if (! Gate::allows('update-post', $post)) { abort(403); } // Actualizar el post... return redirect('/posts');}
Para devolver automáticamente una respuesta 403 cuando el usuario no tiene permiso, utiliza Gate::authorize(). Así te ahorras escribir abort(403).
public function update(Request $request, Post $post): RedirectResponse{ Gate::authorize('update-post', $post); // Si hay permiso llegamos aquí // Actualizar el post... return redirect('/posts');}
Gate::authorize() lanza Illuminate\Auth\Access\AuthorizationException cuando el usuario no tiene permiso. Laravel la convierte automáticamente en una respuesta HTTP 403.
Si quieres conceder todos los permisos a los usuarios administradores, utiliza Gate::before().
use App\Models\User;use Illuminate\Support\Facades\Gate;public function boot(): void{ // $ability contiene el nombre del gate que se intenta ejecutar ('update-post', etc.) Gate::before(function (User $user, string $ability) { if ($user->isAdministrator()) { return true; } }); Gate::define('update-post', function (User $user, Post $post) { return $user->id === $post->user_id; });}
Si la closure de before devuelve un valor distinto de null, ese valor pasa a ser la decisión definitiva. Si devuelve null o no devuelve nada, se continúa con la comprobación habitual del gate.
Las policies agrupan la lógica de autorización relativa a un modelo concreto en una clase. Para gestionar los permisos de creación, lectura, edición y borrado sobre el modelo Post, las policies son más adecuadas que los gates.
Las policies generadas con la opción --model incluyen métodos para las acciones CRUD estándar.
<?phpnamespace App\Policies;use App\Models\Post;use App\Models\User;class PostPolicy{ /** * ¿Puede ver el listado de posts? */ public function viewAny(User $user): bool { return true; // Cualquier usuario autenticado puede verlo } /** * ¿Puede ver un post concreto? */ public function view(User $user, Post $post): bool { return true; // Cualquier usuario autenticado puede verlo } /** * ¿Puede crear posts? */ public function create(User $user): bool { return true; // Cualquier usuario autenticado puede publicar } /** * ¿Puede actualizar el post? */ public function update(User $user, Post $post): bool { return $user->id === $post->user_id; // Solo puede editar los suyos } /** * ¿Puede eliminar el post? */ public function delete(User $user, Post $post): bool { return $user->id === $post->user_id; // Solo puede eliminar los suyos }}
En las policies también puedes definir un método before para conceder todos los permisos a los administradores.
/** * Comprobación previa que se ejecuta antes que los demás métodos de la policy * $ability contiene el nombre del método ('update', 'delete', etc.) */public function before(User $user, string $ability): bool|null{ if ($user->isAdministrator()) { return true; // El administrador tiene permiso para todo } return null; // Devolviendo null se pasa al método habitual}
El método before solo se ejecuta si existe el método correspondiente en la clase de la policy. Por ejemplo, si no está definido update, tampoco se invocará before.
Registrar policies RESTful con authorizeResource()
Llamando a authorizeResource() en el constructor, cada acción del controlador queda asociada automáticamente al método correspondiente de la policy.
<?phpnamespace App\Http\Controllers;use App\Models\Post;class PostController extends Controller{ public function __construct() { $this->authorizeResource(Post::class, 'post'); } // Los métodos index(), show(), create(), store(), edit(), update() y destroy() // aplican automáticamente el método correspondiente de la policy}
Correspondencia entre acciones del controlador y métodos de la policy:
Acción del controlador
Método de la policy
index
viewAny
show
view
create / store
create
edit / update
update
destroy
delete
Con authorizeResource() ya no necesitas llamar a authorize() en cada acción. Resulta especialmente práctico con los resource controllers RESTful.
Para autorizar a nivel de ruta utiliza el middleware can.
use App\Models\Post;// Solo pueden acceder los usuarios con permiso para actualizar el postRoute::put('/posts/{post}', [PostController::class, 'update']) ->middleware('can:update,post');// Solo pueden acceder los usuarios con permiso para crear postsRoute::post('/posts', [PostController::class, 'store']) ->middleware('can:create,App\Models\Post');
Cuando la policy está registrada, las directivas @can y @cannot la utilizan automáticamente.
{{-- El botón de editar solo se muestra a los usuarios autorizados --}}@can('update', $post) <a href="{{ route('posts.edit', $post) }}" class="btn">Editar</a>@endcan{{-- Lo mismo con el botón de eliminar --}}@can('delete', $post) <form method="POST" action="{{ route('posts.destroy', $post) }}"> @csrf @method('DELETE') <button type="submit">Eliminar</button> </form>@endcan{{-- Botón para crear un nuevo post --}}@can('create', App\Models\Post::class) <a href="{{ route('posts.create') }}">Nuevo post</a>@endcan
Ejemplo práctico: permisos de los posts de un blog
Ejemplo de implementación en una aplicación de blog donde «solo el autor puede editar y borrar sus posts».
1
Generar la policy
php artisan make:policy PostPolicy --model=Post
2
Implementar los métodos de la policy
<?phpnamespace App\Policies;use App\Models\Post;use App\Models\User;class PostPolicy{ /** * Concede todos los permisos a los administradores */ public function before(User $user, string $ability): bool|null { if ($user->is_admin) { return true; } return null; } public function viewAny(User $user): bool { return true; } public function view(User $user, Post $post): bool { return true; } public function create(User $user): bool { return true; } 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; }}
3
Añadir authorizeResource() al controlador
<?phpnamespace App\Http\Controllers;use App\Models\Post;use Illuminate\Http\RedirectResponse;use Illuminate\Http\Request;use Illuminate\View\View;class PostController extends Controller{ public function __construct() { $this->authorizeResource(Post::class, 'post'); } public function index(): View { $posts = Post::latest()->paginate(10); return view('posts.index', compact('posts')); } public function store(Request $request): RedirectResponse { $post = $request->user()->posts()->create( $request->validate([ 'title' => ['required', 'string', 'max:255'], 'body' => ['required', 'string'], ]) ); return redirect()->route('posts.show', $post); } public function update(Request $request, Post $post): RedirectResponse { $post->update( $request->validate([ 'title' => ['required', 'string', 'max:255'], 'body' => ['required', 'string'], ]) ); return redirect()->route('posts.show', $post); } public function destroy(Post $post): RedirectResponse { $post->delete(); return redirect()->route('posts.index'); }}
4
Añadir comprobaciones de permisos a la plantilla Blade
Gates: comprobaciones simples que no están asociadas a un modelo. Acceso al panel de administración, permisos globales, etc.
Policies: gestión de permisos CRUD sobre modelos. Crea una policy por recurso (Post, Order, Comment, etc.).
APIs más habituales
// Comprobar mediante gateGate::allows('update-post', $post); // true/falseGate::denies('update-post', $post); // true/falseGate::authorize('update-post', $post); // Lanza excepción si falla// Comprobar desde el modelo User$user->can('update', $post); // true/false$user->cannot('update', $post); // true/false// Comprobar en un controladorGate::authorize('update', $post); // 403 si falla// Comprobar en Blade@can('update', $post) ... @endcan@cannot('update', $post) ... @endcannot
Comandos Artisan habituales
# Generar una policy vacíaphp artisan make:policy PostPolicy# Generarla con los métodos CRUD correspondientes al modelophp artisan make:policy PostPolicy --model=Post