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