Saltar al contenido principal

Diferencia entre autenticación y autorización

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
EscenarioRecomendado
Comprobaciones sencillas que no están asociadas a un modelo concretoGates
Gestión de permisos CRUD sobre un modeloPolicies
Control de acceso al panel de administraciónGates
Permisos de creación, edición y borrado de posts de un blogPolicies

Gates

Los gates son comprobaciones sencillas basadas en closures. Son idóneos para permisos que no están ligados a un modelo concreto.

Flujo de autorización con gates

Definir un gate

Los gates se definen en el método boot de App\Providers\AppServiceProvider con Gate::define().
// app/Providers/AppServiceProvider.php

use 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.

Comprobar permisos con gates

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');
}

Lanzar una excepción con Gate::authorize()

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.

Bypass para administradores (método before)

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.

@can y @cannot en las plantillas Blade

Las directivas @can y @cannot son muy útiles para comprobar gates en las plantillas.
@can('update-post', $post)
    <a href="{{ route('posts.edit', $post) }}">Editar</a>
@endcan

@cannot('update-post', $post)
    <p>No tienes permiso para editar este post.</p>
@endcannot

Policies

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.

Flujo de autorización con policies

Generar una policy

Genera una clase de policy con el comando Artisan make:policy.
php artisan make:policy PostPolicy
Para generar una plantilla con todos los métodos CRUD asociados a un modelo, utiliza la opción --model.
php artisan make:policy PostPolicy --model=Post
Se creará app/Policies/PostPolicy.php.

Detección automática de modelos y policies

Laravel detecta las policies automáticamente siguiendo la convención de nombres.
  • Modelo: app/Models/Post.php
  • Policy: app/Policies/PostPolicy.php
Si respetas esta convención no necesitas registrar manualmente la policy.
Si no sigues la convención de nombres o prefieres registrarla manualmente, utiliza Gate::policy() en el método boot de AppServiceProvider.
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;

Gate::policy(Post::class, PostPolicy::class);
En Laravel 13 también puedes registrarla de forma declarativa añadiendo el atributo #[UsePolicy] al modelo.
use App\Policies\PostPolicy;
use Illuminate\Database\Eloquent\Attributes\UsePolicy;

#[UsePolicy(PostPolicy::class)]
class Post extends Model {}

Implementar los métodos de la policy

Las policies generadas con la opción --model incluyen métodos para las acciones CRUD estándar.
<?php

namespace 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
    }
}

Bypass para administradores (método before)

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.

Orden de ejecución del callback before

Usar policies en el controlador

Método authorize()

Los controladores de Laravel disponen de un helper authorize() (si no usas el controlador base, utiliza Gate::authorize()).
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class PostController extends Controller
{
    public function update(Request $request, Post $post): RedirectResponse
    {
        Gate::authorize('update', $post);

        // Actualizar el post...

        return redirect()->route('posts.index');
    }

    public function store(Request $request): RedirectResponse
    {
        Gate::authorize('create', Post::class);

        // Crear el post...

        return redirect()->route('posts.index');
    }

    public function destroy(Post $post): RedirectResponse
    {
        Gate::authorize('delete', $post);

        $post->delete();

        return redirect()->route('posts.index');
    }
}

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.
<?php

namespace 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 controladorMétodo de la policy
indexviewAny
showview
create / storecreate
edit / updateupdate
destroydelete
Con authorizeResource() ya no necesitas llamar a authorize() en cada acción. Resulta especialmente práctico con los resource controllers RESTful.

Autorización en middleware

Para autorizar a nivel de ruta utiliza el middleware can.
use App\Models\Post;

// Solo pueden acceder los usuarios con permiso para actualizar el post
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->middleware('can:update,post');

// Solo pueden acceder los usuarios con permiso para crear posts
Route::post('/posts', [PostController::class, 'store'])
    ->middleware('can:create,App\Models\Post');
Una sintaxis más concisa mediante el método can:
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->can('update', 'post');

Usar policies en plantillas Blade

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

<?php

namespace 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

<?php

namespace 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

{{-- resources/views/posts/show.blade.php --}}
<h1>{{ $post->title }}</h1>
<p>{{ $post->body }}</p>
<p>Autor: {{ $post->user->name }}</p>

@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}">Editar</a>
@endcan

@can('delete', $post)
    <form method="POST" action="{{ route('posts.destroy', $post) }}">
        @csrf
        @method('DELETE')
        <button type="submit">Eliminar</button>
    </form>
@endcan

Resumen

  • 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.).
// Comprobar mediante gate
Gate::allows('update-post', $post);      // true/false
Gate::denies('update-post', $post);      // true/false
Gate::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 controlador
Gate::authorize('update', $post);        // 403 si falla

// Comprobar en Blade
@can('update', $post) ... @endcan
@cannot('update', $post) ... @endcannot
# Generar una policy vacía
php artisan make:policy PostPolicy

# Generarla con los métodos CRUD correspondientes al modelo
php artisan make:policy PostPolicy --model=Post
Última modificación el 13 de julio de 2026