Introducción

Además de proveer servicios de autenticación por defecto, Laravel además provee una forma simple de autorizar acciones del usuario contra un recurso dado. Como con la autenticación, el enfoque de Laravel para la autorización es simple, y hay dos maneras principales de autorizar acciones: gates y policies (puertas y políticas).

Piensa en los gates y políticas como rutas y controladores. Los Gates proveen una manera simple, basada en funciones anónimas, para definir las reglas de autorización; mientras que las políticas, como los controladores, agrupan la lógica para un modelo o recurso en específico. Vamos a explorar los gates primero y luego las políticas.

No necesitas elegir entre el uso exclusivo de gates o de políticas cuando construyas una aplicación. Lo más probable es que la mayoría de las aplicaciones contengan una mezcla de gates y de políticas ¡Y eso está completamente bien! Los gates son más aplicables a acciones que no estén relacionadas a ningún modelo o recurso, como por ejemplo ver el panel de control de un administrador. Por otro lado, las políticas deberán ser usadas cuando desees autorizar una acción para un modelo o recurso en particular.

Gates

Escribiendo gates

Los gates son funciones anónimas (Closures) que determinan si un usuario está autorizado para ejecutar una acción dada y típicamente son definidos en la clase App\Providers\AuthServiceProvider usando el facade Gate. Los gates siempre reciben la instancia del usuario conectado como el primer argumento y pueden, opcionalmente, recibir argumentos adicionales que sean relevantes, como por ejemplo un modelo de Eloquent:

/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
    $this->registerPolicies();

    Gate::define('edit-settings', function ($user) {
        return $user->isAdmin;
    });

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

Los gates además pueden ser definidos escribiendo la clase y método a llamar como una cadena de texto Class@method, como cuando definimos controladores en las rutas:

/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
    $this->registerPolicies();

    Gate::define('update-post', 'App\Policies\PostPolicy@update');
}

Autorizando acciones

Para autorizar una acción usando gates, deberías usar los métodos allows o denies. Nota que no necesitas pasar el usuario autenticado cuando llames a estos métodos. Laravel se ocupará de esto por ti de forma automática:

if (Gate::allows('edit-settings')) {
    // The current user can edit settings
}

if (Gate::allows('update-post', $post)) {
    // The current user can update the post...
}

if (Gate::denies('update-post', $post)) {
    // The current user can't update the post...
}

Si quisieras determinar si un usuario en particular está autorizado para ejecutar una acción, puedes llamar al método forUser del facade Gate:

if (Gate::forUser($user)->allows('update-post', $post)) {
    // The user can update the post...
}

if (Gate::forUser($user)->denies('update-post', $post)) {
    // The user can't update the post...
}

Puedes autorizar múltiples acciones a la vez con los métodos any o none:

if (Gate::any(['update-post', 'delete-post'], $post)) {
    // The user can update or delete the post
}

if (Gate::none(['update-post', 'delete-post'], $post)) {
    // The user cannot update or delete the post
}

Autorizando o arrojando excepciones

Si te gustaría intentar autorizar una acción y automáticamente arrojar una excepción Illuminate\Auth\Access\AuthorizationException si el usuario no está habilitado para realizar la acción dada, puedes usar el método Gate::authorize. Las instancias de AuthorizationException son convertidas automáticamente a una respuesta HTTP 403:

Gate::authorize('update-post', $post);

// The action is authorized...

Proporcionando contexto adicional

Los métodos gate para autorizar habilidades (allows, denies, check, any, none, authorize, can, cannot) y las directivas de Blade para autorización (@can, @cannot, @canany) pueden recibir un arreglo como segundo argumento. Dichos elementos del arreglo son pasados como parámetros al gate, y pueden ser usados como contexto adicional al tomar decisiones de autorización:

Gate::define('create-post', function ($user, $category, $extraFlag) {
    return $category->group > 3 && $extraFlag === true;
});

if (Gate::check('create-post', [$category, $extraFlag])) {
    // The user can create the post...
}

Respuestas de gates

Hasta el momento, sólo hemos examinado gates que retornan simples valores booleanos. Sin embargo, algunas veces podrías querer retornar una respuesta más detallada, incluyendo un mensaje de error. Para hacer eso, puedes retornar un Illuminate\Auth\Access\Response desde tu gate:

use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

Gate::define('edit-settings', function ($user) {
    return $user->isAdmin
                ? Response::allow()
                : Response::deny('You must be a super administrator.');
});

Al retornar una respuesta de autorización desde tu gate, el método Gate::allows aún retornará un valor booleano simple; sin embargo, puedes usar el método Gate::inspect para obtener la respuesta de autorización completa retornada por el gate:

$response = Gate::inspect('edit-settings', $post);

if ($response->allowed()) {
    // The action is authorized...
} else {
    echo $response->message();
}

Por supuesto, al usar el método Gate::authorize para arrojar una excepción AuthorizationException si la acción no está autorizada, el mensaje de error proporcionado por la respuesta de autorización será propagado a la respuesta HTTP:

Gate::authorize('edit-settings', $post);

// The action is authorized...

Interceptando comprobaciones de gates

Algunas veces, puedes querer otorgar todas las habilidades a un usuario en específico. Puedes usar el método before para definir un callback que sea ejecutado antes de todas las demás comprobaciones de autorización:

Gate::before(function ($user, $ability) {
    if ($user->isSuperAdmin()) {
        return true;
    }
});

Si el callback del before retorna un resultado que no es null, dicho resultado será considerado el resultado de la comprobación.

Puedes usar el método after para definir un callback que será ejecutado luego de todas las demás comprobaciones de autorización:

Gate::after(function ($user, $ability, $result, $arguments) {
    if ($user->isSuperAdmin()) {
        return true;
    }
});

Similar a la comprobación before, si el callback del after retorna un resultado que no sea null, dicho resultado será considerado el resultado de la comprobación.

Creando políticas

Generando políticas

Los políticas son clases que organizan la lógica de autorización para un modelo o recurso en particular. Por ejemplo, si tu aplicación es un blog, puedes tener un modelo Post con su correspondiente PostPolicy para autorizar acciones de usuario como crear o actualizar posts.

Puedes generar una política usando el comando de Artisan make:policy. La política generada será ubicada en el directorio app/Policies. Si el directorio no existe en tu aplicación, Laravel lo creará por ti:

php artisan make:policy PostPolicy

El comando make:policy genera una clase de política vacía. Si quieres generar una clase con los métodos de política para un «CRUD» básico ya incluidos en la clase, puedes especificar la opción --model al ejecutar el comando:

php artisan make:policy PostPolicy --model=Post

Todas las políticas son resueltas a través del contenedor de servicios de Laravel, lo que te permite especificar las dependencias necesarias en el constructor de la política y estas serán automáticamente inyectadas.

Registrando políticas

Una vez que la política exista, ésta necesita ser registrada. La clase AuthServiceProvider incluida con las aplicaciones de Laravel contiene una propiedad policies que mapea tus modelos de Eloquent a sus políticas correspondientes. Registrar una política le indicará a Laravel qué política utilizar para autorizar acciones contra un modelo dado:

<?php

namespace App\Providers;

use App\Policies\PostPolicy;
use App\Post;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    /**
    * The policy mappings for the application.
    *
    * @var array
    */
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    /**
    * Register any application authentication / authorization services.
    *
    * @return void
    */
    public function boot()
    {
        $this->registerPolicies();

        //
    }
}

Política de auto-descubrimiento

En lugar de registrar manualmente políticas de modelos, Laravel puede auto-descubrir políticas siempre y cuando el modelo y la política sigan la convención de nombre estándar de Laravel. Específicamente, las políticas deben estar en un directorio Policies dentro del directorio que contiene los modelos. Así que, por ejemplo, los modelos pueden ser ubicados en el directorio app mientras que las políticas pueden estar situadas en el directorio app/Policies. Adicionalmente, el nombre de la política debe coincidir con el nombre del modelo y tener un sufijo Policy. Así que, a un modelo User le correspondería a una clase UserPolicy.

Si te gustaría proporcionar tu propia lógica para descubrir políticas, puedes registrar un callback personalizado usando el método Gate::guessPolicyNamesUsing. Típicamente, este método debe ser llamado desde el método boot del AuthServiceProvider de tu aplicación:

use Illuminate\Support\Facades\Gate;

Gate::guessPolicyNamesUsing(function ($modelClass) {
    // return policy class name...
});

Cualquier política que esté explícitamente mapeada en tu AuthServiceProvider tendrá precedencia sobre cualquier posible política auto-descubierta.

Escribiendo políticas

Métodos de política

Una vez que la política haya sido registrada, puedes agregar métodos para cada acción a autorizar. Por ejemplo, vamos a definir un método update en nuestro PostPolicy para determinar si un User dado puede actualizar una instancia de un Post.

El método update recibirá una instancia de User y de Post como sus argumentos y debería retornar true o false indicando si el usuario está autorizado para actualizar el Post o no. En el siguiente ejemplo, vamos a verificar si el id del usuario concuerda con el atributo user_id del post:

<?php

namespace App\Policies;

use App\Post;
use App\User;

class PostPolicy
{
    /**
    * Determine if the given post can be updated by the user.
    *
    * @param  \App\User  $user
    * @param  \App\Post  $post
    * @return bool
    */
    public function update(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}

Puedes continuar definiendo métodos adicionales en la política como sea necesario para las diferentes acciones que éste autorice. Por ejemplo, puedes definir métodos view o delete para autorizar varias acciones de Post, pero recuerda que eres libre de darle los nombres que quieras a los métodos de la política.

Si usas la opción --model cuando generes tu política con el comando de Artisan, ésta contendrá métodos para las acciones viewAny, view, create, update, delete, restore y forceDelete.

Respuestas de política

Hasta el momento, sólo hemos examinado métodos de políticas que retornan simples valores booleanos. Sin embargo, algunas veces puedes querer retornar una respuesta más detallada, incluyendo un mensaje de error. Para hacer eso, puedes retornar un Illuminate\Auth\Access\Response desde el método de tu política:

use Illuminate\Auth\Access\Response;

/**
* Determine if the given post can be updated by the user.
*
* @param  \App\User  $user
* @param  \App\Post  $post
* @return \Illuminate\Auth\Access\Response
*/
public function update(User $user, Post $post)
{
    return $user->id === $post->user_id
                ? Response::allow()
                : Response::deny('You do not own this post.');
}

Al retornar una respuesta de autorización desde tu política, el método Gate::allows aún retornará un booleano simple; sin embargo, puedes usar el método Gate::inspect para obtener la respuesta de autorización completa retornada por el gate:

$response = Gate::inspect('update', $post);

if ($response->allowed()) {
    // The action is authorized...
} else {
    echo $response->message();
}

Por supuesto, al usar el método Gate::authorize para arrojar una excepción AuthorizationException si la acción no está autorizada, el mensaje de error proporcionado por la respuesta de autorización será propagado a la respuesta HTTP:

Gate::authorize('update', $post);

// The action is authorized...

Métodos sin modelos

Algunos métodos de políticas sólo reciben el usuario autenticado y no una instancia del modelo que autorizan. Esta situación es común cuando autorizamos acciones create. Por ejemplo, si estás creando un blog, puedes querer revisar si un usuario está autorizado para crear nuevos posts o no.

Cuando definas métodos de política que no recibirán una instancia de otro modelo, así como el método create, debes definir el método con el usuario como único parámetro:

/**
* Determine if the given user can create posts.
*
* @param  \App\User  $user
* @return bool
*/
public function create(User $user)
{
    //
}

Usuarios invitados

Por defecto, todos los gates y políticas automáticamente retornan false si la petición HTTP entrante no fue iniciada por un usuario autenticado. Sin embargo, puedes permitir que estas comprobaciones de autorización atraviesen tus gates y políticas con una declaración de tipo «opcional» o suministrando un valor por defecto null en la definición del argumento de usuario:

<?php

namespace App\Policies;

use App\Post;
use App\User;

class PostPolicy
{
    /**
    * Determine if the given post can be updated by the user.
    *
    * @param  \App\User  $user
    * @param  \App\Post  $post
    * @return bool
    */
    public function update(?User $user, Post $post)
    {
        return optional($user)->id === $post->user_id;
    }
}

Filtros de política

Es posible que quieras autorizar todas las acciones para algunos usuarios en un política dada. Para lograr esto, define un método before en la política. El método before será ejecutado antes que los otros métodos en la política, dándote la oportunidad de autorizar la acción antes que el método destinado de la política sea llamado. Esta característica es comúnmente usada para otorgar autorización a los administradores de la aplicación para que ejecuten cualquier acción:

public function before($user, $ability)
{
    if ($user->isSuperAdmin()) {
        return true;
    }
}

Si quisieras denegar todas las autorizaciones para un usuario deberías retornar false en el método before. Si retornas null, la decisión de autorización recaerá sobre el método de la política.

El método before de una clase de política no será llamado si la clase no contiene un método con un nombre que concuerde con el nombre de la habilidad que está siendo revisada.

Autorizando acciones usando políticas

Vía el modelo de usuario

El modelo User que se incluye por defecto en tu aplicación de Laravel trae dos métodos para autorizar acciones: can y cant (puede y no puede). El método can acepta el nombre de la acción que deseas autorizar y el modelo relevante. Por ejemplo, vamos a determinar si un usuario está autorizado para actualizar un Post dado:

if ($user->can('update', $post)) {
    //
}

Si una política está registrada para el modelo dado, el método can automáticamente llamará a la política apropiada y retornará un resultado booleano. Si no se ha registrado una política para el modelo dado, el método can intentará llamar al Gate basado en Closures que coincida con la acción dada.

Acciones que no requieren modelos

Recuerda, algunas acciones como create pueden no requerir de la instancia de un modelo. En estas situaciones, puedes pasar el nombre de una clase al método can. El nombre de la clase será usado para determinar cuál política usar cuando se autorice la acción:

use App\Post;

if ($user->can('create', Post::class)) {
    // Executes the "create" method on the relevant policy...
}

Vía middleware

Laravel incluye un middleware que puede autorizar acciones antes de que la petición entrante alcance tus rutas o controladores. Por defecto, el middleware Illuminate\Auth\Middleware\Authorize es asignado a la llave can de tu clase App\Http\Kernel. Vamos a explorar un ejemplo usando el middleware can para autorizar que un usuario pueda actualizar un post de un blog:

use App\Post;

Route::put('/post/{post}', function (Post $post) {
    // The current user may update the post...
})->middleware('can:update,post');

En este ejemplo, estamos pasando al middleware can dos argumentos, el primero es el nombre de la acción que deseamos autorizar y el segundo es el parámetro de la ruta que deseamos pasar al método de la política. En este caso, como estamos usando enlazamiento implícito de modelo, un modelo Post será pasado al método de la política. Si el usuario no está autorizado a ejecutar la acción dada, el middleware generará una respuesta HTTP con el código de estado 403.

Acciones que no requieren modelos

Como mencionamos antes, algunas acciones como create pueden no requerir de una instancia de un modelo. En estas situaciones, puedes pasar el nombre de la clase al middleware. El nombre de la clase será usado para determinar cuál política usar para autorizar la acción:

Route::post('/post', function () {
    // The current user may create posts...
})->middleware('can:create,App\Post');

Vía helpers de controladores

Además de proveer métodos útiles en el modelo User, Laravel también provee un método muy útil llamado authorize en cualquier controlador que extienda la clase base App\Http\Controllers\Controller. Como el método can, este método acepta el nombre de la acción que quieras autorizar y el modelo relevante. Si la acción no es autorizada, el método authorize arrojará una excepción de tipo Illuminate\Auth\Access\AuthorizationException, la cual será convertida por el manejador de excepciones por defecto de Laravel en una respuesta HTTP con un código de estado 403:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
    * Update the given blog post.
    *
    * @param  Request  $request
    * @param  Post  $post
    * @return Response
    * @throws \Illuminate\Auth\Access\AuthorizationException
    */
    public function update(Request $request, Post $post)
    {
        $this->authorize('update', $post);

        // The current user can update the blog post...
    }
}

Acciones que no requieren modelos

Como hemos discutido previamente, algunas acciones como create pueden no requerir una instancia de un modelo. En estas situaciones, deberías pasar el nombre de la clase al método authorize. El nombre de la clase determinará la política a usar para autorizar la acción:

/**
* Create a new blog post.
*
* @param  Request  $request
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create(Request $request)
{
    $this->authorize('create', Post::class);

    // The current user can create blog posts...
}

Autorizando controladores de recursos

Si estás utilizando controladores de recursos, puedes hacer uso del método authorizeResource en el constructor del controlador. Este método agregará las definiciones de middleware can apropiadas a los métodos del controlador de recursos.

El método authorizeResource acepta el nombre de clase del modelo como primer argumento y el nombre del parámetro de ruta / petición que contendrá el ID del modelo como segundo argumento:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Post::class, 'post');
    }
}

Los siguientes métodos de controlador serán mapeados con su método de política respectivo:

Método de controlador Método de política
index viewAny
show view
create create
store create
edit update
update update
destroy delete

Puedes usar el comando make:policy con la opción --model para generar rápidamente una clase de política para un modelo dado: php artisan make:policy PostPolicy --model=Post.

Vía plantillas de Blade

Cuando escribas plantillas de Blade, puedes querer mostrar una porción de la página solo si el usuario está autorizado para ejecutar una acción determinada. Por ejemplo, puedes querer mostrar un formulario para actualizar un post solo si el usuario puede actualizar el post. En situaciones así, puedes usar las directivas @can y @cannot:

@can('update', $post)
    <!-- The Current User Can Update The Post -->
@elsecan('create', App\Post::class)
    <!-- The Current User Can Create New Post -->
@endcan

@cannot('update', $post)
    <!-- The Current User Cannot Update The Post -->
@elsecannot('create', App\Post::class)
    <!-- The Current User Cannot Create A New Post -->
@endcannot

Estas directivas son accesos directos convenientes para no tener que escribir sentencias @if y @unless. Las sentencias @can y @cannot anteriores son equivalentes a las siguientes sentencias, respectivamente:

@if (Auth::user()->can('update', $post))
    <!-- The Current User Can Update The Post -->
@endif

@unless (Auth::user()->can('update', $post))
    <!-- The Current User Cannot Update The Post -->
@endunless

También puedes determinar si un usuario tiene habilidad de autorización desde una lista de habilidades dadas. Para lograr esto, usa la directiva @canany:

@canany(['update', 'view', 'delete'], $post)
    // The current user can update, view, or delete the post
@elsecanany(['create'], \App\Post::class)
    // The current user can create a post
@endcanany

Acciones que no requieren modelos

Así como otros métodos de autorización, puedes pasar el nombre de una clase a las directivas @can y @cannot si la acción no requiere una instancia de un modelo:

@can('create', App\Post::class)
    <!-- The Current User Can Create Posts -->
@endcan

@cannot('create', App\Post::class)
    <!-- The Current User Can't Create Posts -->
@endcannot

Proporcionando contexto adicional

Al autorizar acciones usando políticas, puedes pasar un arreglo como segundo argumento a las diferentes funciones y helpers de autorización. El primer elemento en el arreglo será usado para determinar qué política debe ser invocada, mientras que el resto de los elementos del arreglo son pasados como parámetros al método de la política y pueden ser usados como contexto adicional al tomar decisiones de autorización. Por ejemplo, considera la siguiente definición de método PostPolicy que contiene un parámetro adicional $category:

/**
* Determine if the given post can be updated by the user.
*
* @param  \App\User  $user
* @param  \App\Post  $post
* @param  int  $category
* @return bool
*/
public function update(User $user, Post $post, int $category)
{
    return $user->id === $post->user_id &&
           $category > 3;
}

Al intentar determinar si el usuario autenticado puede actualizar un post dado, podemos invocar este método de política de la siguiente manera:

/**
* Update the given blog post.
*
* @param  Request  $request
* @param  Post  $post
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, Post $post)
{
    $this->authorize('update', [$post, $request->input('category')]);

    // The current user can update the blog post...
}

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.

Lección anterior Autenticación de API - Documentación de Laravel 6 Lección siguiente Verificación de Correo Electrónico - Documentación de Laravel 6