Autorização com Policies

Autorização com Policies

Proteja sua Aplicação com Autorizações

Criando Políticas

Políticas são classes que organizam a lógica de autorização em torno de um modelo ou recurso específico. Por exemplo, se seu aplicativo for um blog, você poderá ter um modelo App\Models\Post e um App\Policies\PostPolicy correspondente para autorizar ações do usuário, como criar ou atualizar postagens.

Você pode gerar uma política usando o comando make:policy Artisan. A política gerada será colocada no diretório app/Policies. Se este diretório não existir em sua aplicação, o Laravel irá criá-lo para você:

sail artisan make:policy PostPolicy

O comando make:policy irá gerar uma classe de política vazia. Se você quiser gerar uma classe com exemplos de métodos de política relacionados à visualização, criação, atualização e exclusão do recurso, você pode fornecer uma opção --model ao executar o comando:

sail artisan make:policy PostPolicy --model=Post

Registrando Políticas

Depois que a classe de política for criada, ela precisará ser registrada.
Registrar políticas é como podemos informar ao Laravel qual política usar ao autorizar ações contra um determinado tipo de modelo.

O App\Providers\AuthServiceProvider incluído nas novas aplicações Laravel contém uma propriedade de políticas que mapeia seus modelos Eloquent para suas políticas correspondentes. Registrar uma política instruirá o Laravel qual política utilizar ao autorizar ações contra um determinado modelo do Eloquent:

<?php

namespace App\Providers;

use App\Models\Post;
use App\Policies\PostPolicy;
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.
     */
    public function boot(): void
    {
        // ...
    }
}

Escrevendo Políticas

Métodos de política

Depois que a classe de política for registrada, você poderá adicionar métodos para cada ação que ela autorizar. Por exemplo, vamos definir um método de atualização em nossa PostPolicy que determina se um determinado App\Models\User pode atualizar uma determinada instância de App\Models\Post.

O método update receberá uma instância User e uma Post como argumentos e deverá retornar verdadeiro ou falso indicando se o usuário está autorizado a atualizar o Post fornecido. Portanto, neste exemplo, verificaremos se o id do usuário corresponde ao user_id da postagem:

<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

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

Você pode continuar a definir métodos adicionais na política conforme necessário para as diversas ações que ela autoriza. Por exemplo, você pode definir métodos de visualização ou exclusão para autorizar várias ações relacionadas à postagem, mas lembre-se de que você é livre para dar aos seus métodos de política o nome que desejar.

Se você usou a opção --model ao gerar sua política por meio do console Artisan, ela já conterá métodos para as ações viewAny, view, create, update, delete, restore e forceDelete.

💡
Todas as políticas são resolvidas através do contêiner de serviço Laravel, permitindo que você digite quaisquer dependências necessárias no construtor da política para que sejam injetadas automaticamente.

Policy Responses

Até agora, examinamos apenas métodos de política que retornam valores booleanos simples. No entanto, às vezes você pode desejar retornar uma resposta mais detalhada, incluindo uma mensagem de erro. Para fazer isso, você pode retornar uma instância Illuminate\Auth\Access\Response do seu método de política:

use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;

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

Ao retornar uma resposta de autorização da sua política, o método Gate::allows ainda retornará um valor booleano simples; entretanto, você pode usar o método Gate::inspect para obter a resposta de autorização completa retornada pelo portão:

use Illuminate\Support\Facades\Gate;

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

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

Ao usar o método Gate::authorize, que lança uma AuthorizationException se a ação não for autorizada, a mensagem de erro fornecida pela resposta de autorização será propagada para a resposta HTTP:

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

// The action is authorized...

Personalizando o status da resposta HTTP

Quando uma ação é negada por meio de um método de política, uma resposta HTTP 403 é retornada; entretanto, às vezes pode ser útil retornar um código de status HTTP alternativo. Você pode personalizar o código de status HTTP retornado para uma verificação de autorização com falha usando o construtor estático denyWithStatus na classe Illuminate\Auth\Access\Response:

use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;

/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post): Response
{
    return $user->id === $post->user_id
                ? Response::allow()
                : Response::denyWithStatus(404);
}

Como ocultar recursos por meio de uma resposta 404 é um padrão comum para aplicações web, o método denyAsNotFound é oferecido por conveniência:

use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;

/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post): Response
{
    return $user->id === $post->user_id
                ? Response::allow()
                : Response::denyAsNotFound();
}

Métodos sem modelos

Alguns métodos de política recebem apenas uma instância do usuário atualmente autenticado. Esta situação é mais comum ao autorizar ações de criação. Por exemplo, se você estiver criando um blog, talvez queira determinar se um usuário está autorizado a criar postagens. Nessas situações, seu método de política deverá esperar receber apenas uma instância de usuário:

/**
 * Determine if the given user can create posts.
 */
public function create(User $user): bool
{
    return $user->role == 'writer';
}

Usuários convidados

Por padrão, todos os gates e políticas retornam automaticamente falso se a solicitação HTTP recebida não tiver sido iniciada por um usuário autenticado.

No entanto, você pode permitir que essas verificações de autorização passem para seus gates e policies declarando uma dica de tipo "opcional" ou fornecendo um valor padrão nulo para a definição do argumento do usuário:

<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

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

Filtros de política

Para determinados usuários, você pode autorizar todas as ações dentro de uma determinada política. Para fazer isso, defina um método before na política.
O método before será executado antes de qualquer outro método na política, dando a você a oportunidade de autorizar a ação antes que o método de política pretendido seja realmente chamado. Este recurso é mais comumente usado para autorizar administradores de aplicativos a executar qualquer ação:

use App\Models\User;

/**
 * Perform pre-authorization checks.
 */
public function before(User $user, string $ability): bool|null
{
    if ($user->isAdministrator()) {
        return true;
    }

    return null;
}

Se desejar negar todas as verificações de autorização para um tipo específico de usuário, você poderá retornar false do método before. Se null for retornado, a verificação de autorização passará para o método de política.

O método before de uma classe de política não será chamado se a classe não contiver um método com um nome correspondente ao nome da habilidade que está sendo verificada.

Autorizando ações usando políticas

Através do modelo de usuário

O modelo App\Models\User incluído em sua aplicação Laravel inclui dois métodos úteis para autorizar ações: can e cannot. Os métodos can e cannot não recebem o nome da ação que você deseja autorizar e o modelo relevante. Por exemplo, vamos determinar se um usuário está autorizado a atualizar um determinado modelo App\Models\Post. Normalmente, isso será feito dentro de um método de controlador:

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    /**
     * Update the given post.
     */
    public function update(Request $request, Post $post): RedirectResponse
    {
        if ($request->user()->cannot('update', $post)) {
            abort(403);
        }

        // Update the post...

        return redirect('/posts');
    }
}

Se uma política for registrada para um determinado modelo, o método can chamará automaticamente a política apropriada e retornará o resultado booleano. Se nenhuma política for registrada para o modelo, o método can tentará chamar o Gate baseado em fechamento correspondente ao nome da ação fornecido.

Ações que não requerem modelos

Lembre-se, algumas ações podem corresponder a métodos de política como create que não requerem uma instância de modelo. Nessas situações, você pode passar um nome de classe para o método can. O nome da classe será usado para determinar qual política usar ao autorizar a ação:

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    /**
     * Create a post.
     */
    public function store(Request $request): RedirectResponse
    {
        if ($request->user()->cannot('create', Post::class)) {
            abort(403);
        }

        // Create the post...

        return redirect('/posts');
    }
}

Via Controller Helpers

Além dos métodos úteis fornecidos para o modelo App\Models\User, o Laravel fornece um método de autorização útil para qualquer um dos seus controladores que estendem a classe base App\Http\Controllers\Controller.

Assim como o método can, este método aceita o nome da ação que você deseja autorizar e o modelo relevante. Se a ação não for autorizada, o método authorize lançará uma exceção Illuminate\Auth\Access\AuthorizationException que o manipulador de exceção do Laravel converterá automaticamente em uma resposta HTTP com um código de status 403:

<?php

namespace App\Http\Controllers;

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

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

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

        return redirect('/posts');
    }
}

Ações que não requerem modelos

Conforme discutido anteriormente, alguns métodos de política como create não requerem uma instância de modelo. Nessas situações, você deve passar um nome de classe para o método de autorização. O nome da classe será usado para determinar qual política usar ao autorizar a ação:

use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

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

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

    return redirect('/posts');
}

Autorizando Controladores de Recursos

Se você estiver utilizando controladores de recursos, poderá usar o método authorizeResource no construtor do seu controlador. Este método anexará as definições de middleware apropriadas aos métodos do controlador de recursos.

O método authorizeResource aceita o nome da classe do modelo como seu primeiro argumento, e o nome do parâmetro de rota/solicitação que conterá o ID do modelo como seu segundo argumento. Você deve garantir que seu controlador de recursos seja criado usando o sinalizador --model para que ele tenha as assinaturas de método e dicas de tipo necessárias:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Post;

class PostController extends Controller
{
    /**
     * Create the controller instance.
     */
    public function __construct()
    {
        $this->authorizeResource(Post::class, 'post');
    }
}

Os seguintes métodos de controlador serão mapeados para seu método de política correspondente. Quando as solicitações são roteadas para um determinado método do controlador, o método de política correspondente será automaticamente invocado antes que o método do controlador seja executado:

Controller MethodPolicy Method
indexviewAny
showview
createcreate
storecreate
editupdate
updateupdate
destroydelete

Você pode usar o comando make:policy com a opção --model para gerar rapidamente uma classe de política para um determinado modelo:

sail artesão make:policy PostPolicy --model=Post

Através de Middleware

O Laravel inclui um middleware que pode autorizar ações antes mesmo que a solicitação recebida chegue às suas rotas ou controladores. Por padrão, o middleware Illuminate\Auth\Middleware\Authorize recebe a chave can em sua classe App\Http\Kernel Vamos explorar um exemplo de uso do middleware can para autorizar que um usuário possa atualizar uma postagem:

use App\Models\Post;

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

Neste exemplo, estamos passando dois argumentos ao middleware can. O primeiro é o nome da ação que desejamos autorizar e o segundo é o parâmetro de rota que desejamos passar para o método de política. Nesse caso, como estamos usando vinculação de modelo implícita, um modelo App\Models\Post será passado para o método de política. Caso o usuário não esteja autorizado a realizar a ação determinada, uma resposta HTTP com código de status 403 será retornada pelo middleware.

Por conveniência, você também pode anexar o middleware can à sua rota usando o método can:

use App\Models\Post;

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

Ações que não requerem modelos

Novamente, alguns métodos de política como create não requerem uma instância de modelo. Nessas situações, você pode passar um nome de classe para o middleware. O nome da classe será usado para determinar qual política usar ao autorizar a ação:

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

Especificar o nome inteiro da classe em uma definição de middleware de string pode se tornar complicado. Por esse motivo, você pode optar por anexar o middleware can à sua rota usando o método can:

use App\Models\Post;

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

Via Blade Templates

Ao escrever modelos Blade, você pode desejar exibir uma parte da página somente se o usuário estiver autorizado a executar uma determinada ação. Por exemplo, você pode querer mostrar um formulário de atualização para uma postagem de blog apenas se o usuário puder realmente atualizar a postagem. Nesta situação, você pode usar as diretivas @can e @cannot:

@can('update', $post)
    <!-- The current user can update the post... -->
@elsecan('create', App\Models\Post::class)
    <!-- The current user can create new posts... -->
@else
    <!-- ... -->
@endcan

@cannot('update', $post)
    <!-- The current user cannot update the post... -->
@elsecannot('create', App\Models\Post::class)
    <!-- The current user cannot create new posts... -->
@endcannot

Essas diretivas são atalhos convenientes para escrever instruções @if e @unless. As declarações @can e @cannot acima são equivalentes às seguintes declarações:

@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

Você também pode determinar se um usuário está autorizado a executar qualquer ação de um determinado conjunto de ações. Para fazer isso, use a diretiva @canany:

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

Ações que não requerem modelos

Como a maioria dos outros métodos de autorização, você pode passar um nome de classe para as diretivas @can e @cannot se a ação não exigir uma instância de modelo:

@can('create', App\Models\Post::class)
    <!-- The current user can create posts... -->
@endcan

@cannot('create', App\Models\Post::class)
    <!-- The current user can't create posts... -->
@endcannot

Fornecendo Contexto Adicional

Ao autorizar ações usando políticas, você pode passar um array como segundo argumento para as diversas funções de autorização e auxiliares. O primeiro elemento da matriz será usado para determinar qual política deve ser invocada, enquanto o restante dos elementos da matriz são passados ​​como parâmetros para o método de política e podem ser usados ​​para contexto adicional ao tomar decisões de autorização. Por exemplo, considere a seguinte definição de método PostPolicy que contém um parâmetro $category adicional:

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

Ao tentar determinar se o usuário autenticado pode atualizar uma determinada postagem, podemos invocar este método de política da seguinte forma:

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

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

    return redirect('/posts');
}

Conclusão

Implementar autenticação e autorização no Laravel é uma tarefa simplificada graças às ferramentas e funcionalidades integradas do framework. Com o sistema de autenticação básico e proteção de rotas com middleware, você pode garantir que sua aplicação esteja segura e que os usuários tenham acesso apropriado às diferentes partes do sistema. Nos próximos tópicos, exploraremos outras funcionalidades do Laravel para continuar aprimorando suas habilidades no desenvolvimento de aplicações robustas e eficientes.

Did you find this article valuable?

Support Áreadev by becoming a sponsor. Any amount is appreciated!