Capítulo 10: Autorização com Gates, Policies e Role-Based Access Control
Especialização em Backend com PHP & Laravel • Laravel 11 & PHP 8.3+ • Eloquent ORM, Sanctum, Filas, Horizon e APIs Corporativas
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Gate::define('acao', closure)"] --> B["Ability solta, sem modelo específico"]
C["ProdutoPolicy::update(user, produto)"] --> D["Ability atrelada a um modelo Eloquent"]
D --> E["before(user, ability) — bypass de Super Admin"]
B --> F["$this->authorize('acao', $modelo)"]
D --> F
F --> G["403 automático via AuthorizationException"]
subgraph RBAC["RBAC em Escala"]
H["spatie/laravel-permission"]
I["$user->assignRole('admin')"]
J["middleware('permission:faturar-pedidos')"]
end
G --> RBAC
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style C fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style H fill:#fce4ec,stroke:#e91e63,stroke-width:2px
🏛️ 1. Gates: Autorização como Closures
Gates são a forma mais simples de autorização no Laravel: closures registradas em AppServiceProvider::boot() que recebem o usuário e retornam um booleano. São ideais para ações que não giram em torno de um modelo Eloquent específico:
<?php
Gate::define('gerenciar-produtos', function (User $user) {
return $user->is_admin === true;
});
if (Gate::allows('gerenciar-produtos')) { /* ... */ }
🧩 2. Policies: Autorização Orientada a Modelos
Quando a regra depende de comparar o usuário com uma instância de modelo (o post é do próprio usuário? o pedido pertence ao cliente logado?), uma Policy dedicada organiza isso melhor que Gates soltos. O Laravel convenciona métodos view, create, update, delete mapeados às ações CRUD:
<?php
// app/Policies/ProdutoPolicy.php
class ProdutoPolicy
{
public function update(User $user, Produto $produto): bool
{
return $user->id === $produto->user_id || $user->is_admin;
}
}
No controller, $this->authorize() resolve automaticamente qual Policy usar com base no tipo do modelo, e lança AuthorizationException (convertida em HTTP 403) se a regra retornar false:
public function update(Request $request, Produto $produto)
{
$this->authorize('update', $produto);
// só chega aqui se autorizado
}
O método before()
Repetir “ou é admin” em toda regra de toda Policy é ruído. O método especial before() roda antes de qualquer outro método da Policy e intercepta a decisão:
public function before(User $user, string $ability): ?bool
{
if ($user->hasRole('SUPER_ADMIN')) {
return true; // aprova QUALQUER ability, sem checar as regras individuais
}
return null; // null = "não decidi", segue para o método específico (ex: update())
}
Retornar null (não false) é o que permite a Policy continuar a avaliação normal para usuários que não são super admin.
🏢 3. RBAC Corporativo com Spatie Laravel-Permission
Gates e Policies resolvem autorização por regra de negócio, mas não modelam papéis e permissões dinâmicos gerenciados pelo banco (um admin cadastra novos cargos sem deploy de código). O pacote spatie/laravel-permission é o padrão de mercado para isso: adiciona a trait HasRoles ao User e persiste roles/permissions em tabelas próprias, com cache automático para não penalizar performance:
$user->assignRole('admin');
$user->hasPermissionTo('editar-produtos'); // true/false
// routes/api.php
Route::middleware(['permission:faturar-pedidos'])->group(function () {
Route::post('/pedidos/{pedido}/faturar', FaturarController::class);
});
A diferença estrutural: Gates/Policies são código versionado; Spatie é dado, o que o torna a escolha certa quando o RBAC precisa mudar em produção sem novo deploy — ao custo de uma camada extra de indireção que precisa ser mantida em sincronia com o domínio da aplicação.