Policy e Gate: autorização granular
1. Fundamentos da Autorização no Laravel
1.1 Autorização vs Autenticação
Autenticação responde "quem é você?", enquanto autorização responde "o que você pode fazer?". No Laravel, Gates e Policies são os mecanismos principais para implementar autorização granular, permitindo controlar acesso a recursos específicos com base em regras de negócio.
1.2 Visão Geral do Sistema
O sistema de autorização do Laravel é centrado no serviço Gate, que gerencia todas as verificações de permissão. Diferente do Auth::check() (que apenas verifica se o usuário está logado), Gates e Policies permitem definir regras complexas.
// Verificação básica vs autorização granular
Auth::check(); // apenas autenticação
Gate::allows('update-post', $post); // autorização específica
1.3 Registro no AuthServiceProvider
Tanto Gates quanto Policies são registrados no AuthServiceProvider:
// app/Providers/AuthServiceProvider.php
public function boot(): void
{
// Registro de Gate
Gate::define('edit-settings', function (User $user) {
return $user->is_admin;
});
// Registro de Policy
Gate::policy(Post::class, PostPolicy::class);
}
2. Gates: Autorizações Simples
2.1 Criando Gates com Closures
Gates são ideais para autorizações simples que não justificam uma classe Policy inteira:
// No AuthServiceProvider
Gate::define('view-reports', function (User $user) {
return $user->hasRole('manager');
});
Gate::define('export-data', function (User $user, string $format) {
return $user->canExportTo($format);
});
2.2 Verificações com Gates
// No controller
if (Gate::allows('view-reports')) {
// Usuário pode ver relatórios
}
if (Gate::denies('export-data', 'pdf')) {
abort(403, 'Exportação PDF não permitida');
}
// Lança exceção automaticamente
Gate::authorize('view-reports');
2.3 Gates com Múltiplos Parâmetros
// Definição
Gate::define('manage-category', function (User $user, $category, $action) {
return $user->canManage($category, $action);
});
// Uso
Gate::authorize('manage-category', [$category, 'delete']);
3. Policies: Classes de Autorização para Modelos
3.1 Gerando Policies
php artisan make:policy PostPolicy --model=Post
3.2 Métodos Padrão
// app/Policies/PostPolicy.php
class PostPolicy
{
public function viewAny(User $user): bool
{
return true; // Todos podem listar
}
public function view(User $user, Post $post): bool
{
return $user->id === $post->user_id || $post->is_public;
}
public function create(User $user): bool
{
return $user->hasVerifiedEmail();
}
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 && !$post->is_pinned;
}
public function restore(User $user, Post $post): bool
{
return $user->hasRole('admin');
}
public function forceDelete(User $user, Post $post): bool
{
return $user->hasRole('super-admin');
}
}
3.3 Injeção de Dependências
public function update(User $user, Post $post, Logger $logger): bool
{
$logger->info("Verificando permissão de atualização");
return $user->id === $post->user_id;
}
4. Integração com Controllers e Views
4.1 Autorização no Controller
// app/Http/Controllers/PostController.php
public function update(Request $request, Post $post)
{
// Autorização inline
$this->authorize('update', $post);
// Ou com modelo explícito
$this->authorize('update', Post::class);
// Lógica de atualização
$post->update($request->validated());
}
4.2 Diretivas Blade
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Editar</a>
@elsecan('view', $post)
<span>Visualizar apenas</span>
@endcan
@cannot('delete', $post)
<span>Exclusão não permitida</span>
@endcannot
4.3 Middleware em Rotas
// Rotas protegidas
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware('can:update,post');
Route::delete('/posts/{post}', [PostController::class, 'destroy'])
->middleware('can:delete,post');
5. Autorização Granular com Condições Complexas
5.1 Verificações Baseadas em Relacionamentos
// Policy para Team
class TeamPolicy
{
public function addMember(User $user, Team $team): bool
{
return $user->id === $team->owner_id ||
$team->members()->where('user_id', $user->id)
->where('role', 'admin')->exists();
}
public function removeMember(User $user, Team $team, User $member): bool
{
return ($user->id === $team->owner_id) ||
($user->id !== $member->id &&
$team->isAdmin($user) &&
!$team->isOwner($member));
}
}
5.2 Políticas com Papéis e Permissões
public function publish(User $user, Post $post): bool
{
if ($user->hasPermissionTo('publish-posts')) {
return true;
}
if ($post->status === 'draft' && $user->id === $post->user_id) {
return $user->hasAnyRole(['editor', 'admin']);
}
return false;
}
5.3 Regras Globais com before() e after()
class PostPolicy
{
// Executa antes de qualquer método
public function before(User $user, string $ability): ?bool
{
if ($user->hasRole('super-admin')) {
return true; // Super admin tem acesso total
}
return null; // Continua para o método específico
}
// Executa depois de qualquer método
public function after(User $user, string $ability, bool $result): bool
{
if ($user->hasRole('auditor') && $ability === 'view') {
return true; // Auditores sempre podem visualizar
}
return $result;
}
}
6. Respostas e Tratamento de Exceções
6.1 Personalizando Respostas de Erro
// No Policy
use Illuminate\Auth\Access\Response;
public function delete(User $user, Post $post): Response
{
if ($user->id !== $post->user_id) {
return Response::deny('Você não é o autor deste post.');
}
if ($post->is_pinned) {
return Response::deny('Posts fixados não podem ser excluídos.');
}
return Response::allow();
}
6.2 Inspeção de Autorização
// Gate::inspect() retorna resposta detalhada
$response = Gate::inspect('update', $post);
if ($response->allowed()) {
// Prosseguir
} else {
$message = $response->message(); // Mensagem personalizada
$code = $response->code(); // Código de erro
}
// Gate::raw() retorna boolean puro
$canUpdate = Gate::raw('update', $post);
7. Testes de Autorização
7.1 Testando Gates com Fake
public function test_gate_authorization()
{
Gate::fake();
// Simular autorização
Gate::define('edit-post', function () {
return true;
});
$response = $this->actingAs(User::factory()->create())
->put('/posts/1', ['title' => 'Novo']);
Gate::assertAuthorized('edit-post');
}
7.2 Testando Policies
public function test_post_policy()
{
$user = User::factory()->create();
$post = Post::factory()->create(['user_id' => $user->id]);
$this->assertTrue($user->can('update', $post));
$this->assertTrue($user->can('delete', $post));
$otherUser = User::factory()->create();
$this->assertFalse($otherUser->can('update', $post));
}
7.3 Simulação Completa
public function test_authorization_in_feature_test()
{
$admin = User::factory()->create(['role' => 'admin']);
$post = Post::factory()->create();
$response = $this->actingAs($admin)
->delete("/posts/{$post->id}");
$response->assertStatus(200);
$regularUser = User::factory()->create(['role' => 'user']);
$response = $this->actingAs($regularUser)
->delete("/posts/{$post->id}");
$response->assertStatus(403);
}
8. Boas Práticas e Performance
8.1 Organização por Domínio
// app/Policies/Admin/
// app/Policies/Content/
// app/Policies/Finance/
// Reutilização de lógica
trait AdminAuthorization
{
public function before(User $user): ?bool
{
return $user->hasRole('admin') ? true : null;
}
}
class ContentPolicy
{
use AdminAuthorization;
public function create(User $user): bool
{
return $user->hasVerifiedEmail();
}
}
8.2 Cache de Autorização
// Cache para evitar consultas repetidas
public function view(User $user, Post $post): bool
{
return Cache::remember(
"user.{$user->id}.post.{$post->id}.view",
3600,
fn() => $user->id === $post->user_id || $post->is_public
);
}
// Uso de before() para cache global
public function before(User $user): ?bool
{
if ($cached = Cache::get("user.{$user->id}.permissions")) {
return $cached['super_admin'] ? true : null;
}
return null;
}
8.3 Evitando Redundância
// Form Request com autorização embutida
class UpdatePostRequest extends FormRequest
{
public function authorize(): bool
{
return Gate::allows('update', $this->route('post'));
}
public function rules(): array
{
return [
'title' => 'required|max:255',
'content' => 'required',
];
}
}
// Controller limpo
public function update(UpdatePostRequest $request, Post $post)
{
$post->update($request->validated());
return redirect()->route('posts.show', $post);
}
Referências
- Laravel Documentation - Authorization — Documentação oficial sobre Gates e Policies no Laravel
- Laravel Daily - Laravel Authorization: Gates vs Policies — Tutorial comparativo entre Gates e Policies
- Laravel News - Laravel Authorization: A Practical Guide — Guia prático de autorização no Laravel com exemplos reais
- Spatie - Laravel Permission Package — Pacote popular para gerenciamento de roles e permissions
- Laravel Bootcamp - Authorization Chapter — Tutorial interativo oficial sobre autorização no Laravel
- Laracasts - Laravel Authorization — Screencast sobre autorização no Laravel (conteúdo premium)
- Stack Overflow - Laravel Policy vs Gate — Discussão técnica sobre diferenças entre Policies e Gates