Laravelにおける認可機能は、ユーザーの権限を管理するための重要な部分です。本記事では、Laravelの「Gate(ゲート)」について、基本的な使い方から実際のコード例を通じて学んでいきます。
Laravel Gate とは?
Laravel Gateは、ユーザーが特定のアクションを実行することを許可するかどうかを判断するための仕組みです。簡単に言うと、「このユーザーはこの操作をしてもよいのか?」という問いに対する答えを返します。Gateは特定のモデルやリソースに依存せず、特に全ユーザー共通の機能へのアクセスを制御するのに適しています。
たとえば、管理者だけが見られるダッシュボードや、投稿の編集権限を持つユーザーを制限する場合など、特定のアクションに対する権限確認を行います。Gateを使用することで、ユーザーが持つべき権限を簡単に定義し、その権限に基づいてアクションを制御できます。
準備
Postモデルの作成
まず、Laravelプロジェクトを作成してください。
次に、記事モデルを作成し、マイグレーションを編集します。
php artisan make:model Post -m
以下のように、posts
テーブルのマイグレーションファイルを編集します。
// database/migrations/xxxx_xx_xx_create_posts_table.php
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->text('content');
$table->timestamps();
});
}
マイグレーションを実行します。
php artisan migrate
認証基盤のインストール
認証を導入していないとGateが機能しないので、簡単に実装できるLaravel Breezeを使用します。
下記コマンドを順に流していけば、ログイン画面が作成されます。
composer require laravel/breeze --dev
php artisan breeze:install
php artisan migrate
npm install
npm run dev
ユーザー登録
http://localhost/register
から、ユーザーを登録します。ログインできればOKです。
続けてあと1人のユーザーを登録しておいてください。
その後、最初に作成したユーザーで再度ログインしておきます。
Gateの定義
次に、Gateを定義します。App\Providers\AppServiceProvider
.php に以下のコードを追加します
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
Gate::define('update-post', function (User $user, Post $post) {
return $user->id === $post->user_id;
});
}
Gate::defineの第二引数、コールバックでは常に最初の引数としてユーザー インスタンスを受け取ります。その後にオプションで、関連するEloquent モデルなどの追加の引数を受け取ることもできます。
ここでは、ユーザーが特定の投稿を更新できるかどうかをチェックするGateを定義しました。
自分の投稿のみ、編集できるような制御を加えています。
アクションの認可
次に、実際にアクションを処理するためのコントローラを作成します。以下のコマンドで PostController
を作成します。
php artisan make:controller PostController
以下のコードを PostController
に追加し、Gateを使ってアクションを認可します。
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
class PostController extends Controller
{
public function update(Request $request, Post $post)
{
if (! Gate::allows('update-post', $post)) {
abort(403, 'この投稿を更新する権限がありません。');
}
$post->title = $request->input('title');
$post->content = $request->input('content');
$post->save();
return redirect()->route('posts.index')->with('success', '投稿が更新されました。');
}
}
ここでは、Gateを使用して権限を確認し、無ければ403エラーを返します。
ルートの設定
routes/web.phpにルートを設定します。
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
// 追加
Route::get('/posts', function () {
$posts = Post::all();
return view('posts.index', compact('posts'));
})->name('posts.index');
Route::get('/posts/edit/{post}', function (Post $post) {
return view('posts.edit', compact('post'));
})->name('posts.edit');
Route::put('/posts/{post}', [PostController::class, 'update'])->name('posts.update');
// 追加ここまで
});
ビューの作成
resources/views
ディレクトリ内に posts
フォルダを作成し、その中に index.blade.php
と edit.blade.php
を作成します。
index.blade.php(一覧画面)
<!-- resources/views/posts/index.blade.php -->
<!DOCTYPE html>
<html>
<head>
<title>ポスト一覧</title>
</head>
<body>
<h1>投稿一覧</h1>
<!-- フラッシュメッセージの表示 -->
@if (session('success'))
<div style="color: green; font-weight: bold;">
{{ session('success') }}
</div>
@endif
<ul>
@foreach ($posts as $post)
<li>
<strong>{{ $post->title }}</strong>
<a href="{{ route('posts.edit', $post) }}">編集</a>
</li>
@endforeach
</ul>
</body>
</html>
edit.blade.php(編集画面)
<!-- resources/views/posts/edit.blade.php -->
<!DOCTYPE html>
<html>
<head>
<title>投稿編集</title>
</head>
<body>
<h1>投稿編集</h1>
@if ($errors->any())
<div style="color: red;">
{{ $errors->first() }}
</div>
@endif
<form action="{{ route('posts.update', $post) }}" method="POST">
@csrf
@method('PUT')
<label for="title">タイトル:</label>
<input type="text" name="title" value="{{ $post->title }}">
<label for="content">内容:</label>
<textarea name="content">{{ $post->content }}</textarea>
<button type="submit">更新</button>
</form>
</body>
</html>
ダミー投稿の作成
シーダーを使うことで、データベースに簡単にテストデータを挿入できます。
まず、シーダーを作成します。次のコマンドを実行します。
php artisan make:seeder PostSeeder
database/seeders/PostSeeder.php
ファイルが作成されたら、以下のように編集します。
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Post;
class PostSeeder extends Seeder
{
public function run()
{
// ダミー投稿の作成
Post::create([
'user_id' => 1, // 先に作成したユーザーID
'title' => 'ダミー投稿1',
'content' => 'これはテスト用の投稿です。'
]);
Post::create([
'user_id' => 2, // 他のユーザーID
'title' => 'ダミー投稿2',
'content' => 'これはテスト用の投稿です。'
]);
Post::create([
'user_id' => 2, // 他のユーザーID
'title' => 'ダミー投稿3',
'content' => 'これはテスト用の投稿です。'
]);
}
}
作成したシーダーを呼び出します。以下のコマンドを実行してください。
php artisan db:seed --class=PostSeeder
このコマンドを実行すると、PostSeeder
が実行され、ダミー投稿がデータベースに挿入されます。
確認
http://localhost/posts
を開き、一覧画面が出ることを確認します。
ダミー投稿1を開いて更新をかけると、
このように、更新することができました。
次に、他の人の投稿を開いて更新をかけにいきます。
このように、403のエラー画面に遷移することが確認できました。
データベースの投稿の内容も更新されていません。
注意点
Gateは簡単に認可を実装することができます。
しかし一方で、公式ドキュメントにもある通り、より複雑なアプリケーションを構築する上ではPolicyでの実装も検討してください。
Gateは Laravel の認可機能の基礎を学ぶのに最適な方法ですが、堅牢な Laravel アプリケーションを構築する場合は、
https://laravel.com/docs/11.x/authorization#writing-gates
Policyを使用して認可ルールを整理することを検討する必要があります。
まとめ
LaravelのGateを利用することで、ユーザーのアクションに対する権限を柔軟に管理することができます。特に、状況に応じてユーザーごとの権限を設定したり、特定のリソースに対してのアクセスを制御する際に役立ちます。この記事を通じて、Gateの使い方やその効果を具体的なコード例を通して学べたこと思います。