Autorização com Gates
Como Usar Gates no Laravel para Garantir que Apenas Usuários Autorizados Acessem Seus Recursos
Requisitos
Código das Migrations:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->text('content');
$table->timestamps();
});
Execute:
sail artisan migrate
Banco de dados
Seeder
sail artisan make:seeder UsersTableSeeder
sail artisan make:seeder PostsTableSeeder
UsersTableSeeder
use Illuminate\Support\Facades\DB;
DB::table('users')->insert([
[
'name' => 'areadev',
'email' => 'areadev@gmail.com',
'password' => Hash::make('12341234'),
],
[
'name' => 'bruno',
'email' => 'bruno@gmail.com',
'password' => Hash::make('12341234'),
],
]);
PostsTableSeeder
use Illuminate\Support\Facades\DB;
// Obter o ID dos usuários
$userIds = DB::table('users')->pluck('id');
// Criar posts para cada usuário
foreach ($userIds as $userId) {
DB::table('posts')->insert([
[
'user_id' => $userId,
'title' => 'Post Title ' . $userId . ' - 1',
'content' => 'This is the content for post 1 of user ' . $userId,
],
[
'user_id' => $userId,
'title' => 'Post Title ' . $userId . ' - 2',
'content' => 'This is the content for post 2 of user ' . $userId,
],
]);
}
DatabaseSeeder
$this->call([
FruitsTableSeeder::class,
UsersTableSeeder::class,
PostsTableSeeder::class,
]);
Model User
/* adicione o seguinte código no final do arquivo. */
/**
* Get the posts for the user.
*/
public function posts()
{
return $this->hasMany(Post::class);
}
Model Post
protected $fillable = [
'user_id',
'title',
'content',
];
/**
* Get the user that owns the post.
*/
public function user()
{
return $this->belongsTo(User::class);
}
Objetivo
Implementar sistemas de autenticação e autorização para proteger e gerenciar o acesso à aplicação é crucial para qualquer aplicação web. No Laravel, essas funcionalidades são integradas de maneira eficiente e segura, permitindo que você configure e gerencie o acesso dos usuários com facilidade.
Authorization
Além de fornecer serviços de autenticação integrados, o Laravel também fornece uma maneira simples de autorizar ações do usuário em um determinado recurso.
Por exemplo, mesmo que um usuário seja autenticado, ele pode não estar autorizado a atualizar ou excluir determinados modelos do Eloquent ou registros de banco de dados gerenciados pela sua aplicação. Os recursos de autorização do Laravel fornecem uma maneira fácil e organizada de gerenciar esses tipos de verificações de autorização.
O Laravel fornece duas formas principais de autorizar ações: gates e policies.
Pense em gates e policies como rotas e controladores.
Gates fornece uma abordagem simples e baseada em fechamento para autorização, enquanto políticas, como controladores, agrupam lógica em torno de um modelo ou recurso específico.
Você não precisa escolher entre usar exclusivamente gates ou usar exclusivamente policies ao criar um aplicativo. A maioria dos aplicativos provavelmente conterá alguma mistura de gates e policies, e isso é perfeitamente normal!
Gates são mais aplicáveis a ações que não estão relacionadas a nenhum modelo ou recurso, como visualizar um painel de administrador. Por outro lado, as políticas devem ser utilizadas quando se deseja autorizar uma ação para um modelo ou recurso específico.
Gates
Gates são simplesmente fechamentos que determinam se um usuário está autorizado a realizar uma determinada ação. Normalmente, os gates são definidos no método de inicialização da classe App\Providers\AuthServiceProvider
usando a facade Gate.
Gates sempre recebe uma instância de usuário como seu primeiro argumento e pode opcionalmente receber argumentos adicionais, como um modelo Eloquent relevante.
Neste exemplo, definiremos um gate para determinar se um usuário pode atualizar um determinado modelo App\Models\Post
. O gate fará isso comparando o id do usuário com o user_id do usuário que criou a postagem:
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AppServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
* As políticas são registradas em um array associativo.
* Onde a chave é o modelo e o valor é a classe da política.
* @var array<class-string, class-string>
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
/**
* define um Gate, que é uma maneira de verificar se um usuário tem permissão para realizar uma ação específica.
* A função anônima (closure) que você fornece define a lógica de autorização. Ela recebe dois parâmetros: um User e um Post.
*
* Parâmetros:
* User $user: O usuário que está tentando realizar a ação.
* Post $post: O modelo Post sobre o qual a ação está sendo realizada.
*/
Gate::define('update-post', function (User $user, Post $post) {
return $user->id === $post->user_id;
});
}
}
Assim como os controladores, os gates também podem ser definidos usando um array de retorno de chamada de classe:
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
Gate::define('update-post', [PostPolicy::class, 'update']);
}
Autorizando Ações
Para autorizar uma ação usando gates, você deve usar os métodos de permissão ou negação fornecidos pela facade Gate. Observe que você não é obrigado a passar o usuário atualmente autenticado para esses métodos. O Laravel cuidará automaticamente de passar o usuário para o fechamento do gate. É comum chamar os métodos de autorização de gate nos controladores da sua aplicação antes de executar uma ação que exija autorização:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
class PostController extends Controller
{
public function index()
{
// Busca todos os posts
$posts = Post::with('user')->get();
// Retorna a view com os posts
return view('posts.index', compact('posts'));
}
public function edit(Post $post)
{
// Verifica se o usuário pode editar o post
// allows - permite
if (Gate::allows('update-post', $post)) {
return view('posts.edit', compact('post'));
} else {
abort(403, 'Você não tem permissão para editar este post.');
}
}
public function update(Request $request, Post $post): RedirectResponse
{
// Verifique se o usuário pode atualizar o post
if (Gate::allows('update-post', $post)) {
// Valida os dados
$validated = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
]);
// Atualiza o post
$post->update($validated);
return redirect('/posts');
} else {
abort(403, 'Você não tem permissão para atualizar este post.');
}
}
}
Crie as views necessárias:
/* resources/views/posts/edit.blade.php */
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Editar Post</h1>
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
<form action="{{ route('post.update', $post) }}" method="POST">
@csrf
@method('PATCH')
<div class="mb-3">
<label for="title" class="form-label">Título</label>
<input type="text" class="form-control" id="title" name="title" value="{{ old('title', $post->title) }}" required>
</div>
<div class="mb-3">
<label for="content" class="form-label">Conteúdo</label>
<textarea class="form-control" id="content" name="content" rows="5" required>{{ old('content', $post->content) }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Atualizar</button>
</form>
</div>
@endsection
/* resources/views/posts/index.blade.php */
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Todos os Posts</h1>
<table class="table">
<thead>
<tr>
<th>Título</th>
<th>Conteúdo</th>
<th>Autor</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
@foreach ($posts as $post)
<tr>
<td>{{ $post->title }}</td>
<td>{{ $post->content }}</td>
<td>{{ $post->user->name }}</td>
<td>
<!-- Botão de edição -->
<a href="{{ route('post.edit', $post) }}" class="btn btn-primary">Editar</a>
<!-- Formulário de exclusão -->
<form action="{{ route('post.delete', $post) }}" method="POST" style="display: inline;" onsubmit="return confirm('Tem certeza que deseja excluir este post?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Deletar</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
@if (Auth::user()->can('view-users'))
<a href="{{ url('/users') }}">Lista de usuários</a>
@endif
</div>
@endsection
/* Rotas */
Route::middleware('auth')->group(function () {
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::get('/post/{post}/edit', [PostController::class, 'edit'])->name('post.edit');
Route::patch('/post/{post}', [PostController::class, 'update'])->name('post.update');
Route::delete('/posts/{post}', [PostController::class, 'destroy'])->name('post.delete');
});
Veja na imagem abaixo a rota posts!
Veja na imagem abaixo a rota para editar um post especifico!
Autorizando ou lançando exceções
Se você quiser tentar autorizar uma ação e lançar automaticamente uma Illuminate\Auth\Access\AuthorizationException se o usuário não tiver permissão para executar a ação dada, você pode usar o método de autorização da fachada Gate.
Instâncias de AuthorizationException são automaticamente convertidas em uma resposta HTTP 403 pelo manipulador de exceções do Laravel:
Gate::authorize('update-post', $post);
// The action is authorized...
Exemplo, no método destroy:
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
class PostController extends Controller
{
// outros métodos ...
public function destroy(Post $post)
{
// Verifica se o usuário pode deletar o post
if (Gate::authorize('delete-post', $post)) {
// O usuário pode deletar o post
$post->delete();
return redirect('/posts');
} else {
// O usuário não tem permissão para deletar o post
abort(403, 'Você não tem permissão para excluir este post.');
}
}
}
Resultado. lança automaticamente uma Illuminate\Auth\Access\AuthorizationException:
Interceptando verificações de gate
Às vezes, você pode querer conceder todas as habilidades a um usuário específico. Você pode usar o método before
para definir um fechamento que será executado antes de todas as outras verificações de autorização:
use App\Models\User;
use Illuminate\Support\Facades\Gate;
Gate::before(function (User $user, string $ability) {
if ($user->isAdministrator()) {
return true;
}
});
Se o fechamento anterior retornar um resultado não nulo, esse resultado será considerado o resultado da verificação de autorização.
Vamos criar um exemplo prático para entendermos o uso do Gate::before.
Route::middleware('auth')->group(function () {
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::get('/post/{post}/edit', [PostController::class, 'edit'])->name('post.edit');
Route::patch('/post/{post}', [PostController::class, 'update'])->name('post.update');
Route::delete('/posts/{post}', [PostController::class, 'destroy'])->name('post.delete');
// Crie as rotas abaixo:
Route::get('/users', [UserController::class, 'index'])->name('users.index');
Route::post('/users/{user}/toggle-admin', [UserController::class, 'toggleAdmin'])->name('users.toggleAdmin');
});
Crie um controller para o user:
sail artisan make:controller UserController
Código completo do UserController:
<?php
namespace App\Http\Controllers;
use App\Models\User;
class UserController extends Controller
{
public function index()
{
$users = User::all();
return view('users.index', compact('users'));
}
public function toggleAdmin(User $user)
{
$user->is_admin = !$user->is_admin;
$user->save();
return redirect()->route('users.index')->with('success', 'Status de administrador alterado com sucesso.');
}
}
Vamos criar uma view para listar os usuários:
// resources/views/users/index.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Lista de Usuários</h1>
@if (session('success'))
<div style="color: green;">
{{ session('success') }}
</div>
@endif
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>Nome</th>
<th>Email</th>
<th>Administrador</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
@foreach ($users as $user)
<tr>
<td>{{ $user->id }}</td>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>{{ $user->is_admin ? 'Sim' : 'Não' }}</td>
<td>
<form action="{{ route('users.toggleAdmin', $user) }}" method="POST">
@csrf
<button type="submit">
{{ $user->is_admin ? 'Remover Admin' : 'Tornar Admin' }}
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
<a href="{{ url('/posts') }}">Ver Postagens</a>
</div>
@endsection
Agora na model User, adicione o seguinte código:
public function isAdministrator()
{
return $this->is_admin; // Assume que 'is_admin' é uma coluna booleana na tabela 'users'
}
Vamos criar uma migration para adicionar a coluna is_admin na tabela users:
sail artisan make:migration add_is_admin_to_users_table --table=users
Schema::table('users', function (Blueprint $table) {
// Adiciona a coluna is_admin após a coluna email
$table->boolean('is_admin')->default(false)->after('email');
});
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('is_admin');
});
Execute o comando abaixo para atualizar a tabela users:
sail artisan migrate
Veja que foi adicionado a coluna is_admin com o valor 0.
Altere para 1 o primeiro usuário:
Agora no front, observe que o usuário autenticado é o areadev e ele pode editar os posts do usuário bruno.
Vamos definir um gate para visualizar a opção "Lista de usuários" quando o user autenticado for admin.
Gate::define('view-users', function (User $user) {
return $user->is_admin;
});
Observe que para o usuário que não é admin, não aparece a opção "Lista de usuários" no canto inferior esquerdo da tela.
O método Gate::after
permite que você execute uma lógica depois de todas as outras verificações de autorização. Isso é útil para fazer log ou auditoria das decisões de autorização, ou para aplicar regras adicionais depois das verificações normais:
use App\Models\User;
use Illuminate\Support\Facades\Gate;
Gate::after(function (User $user, $ability, $result, $arguments) {
Log::info('Autorização: ', [
'user_id' => $user->id,
'ability' => $ability,
'result' => $result,
'arguments' => $arguments,
]);
});
Veja o arquivo laravel.log
dentro da pasta storage/logs
Semelhante ao método before, se o fechamento posterior retornar um resultado não nulo, esse resultado será considerado o resultado da verificação de autorização.