#08 FilamentPHP基礎

認証とアクセス制御——ポリシーとロールで管理する

Filamentのアクセス制御の仕組み

Filamentのアクセス制御は2層構造になっています。

1. パネルへのアクセス   → User::canAccessPanel()
2. リソース操作の制御   → Resource::canXxx() または Laravel Policy

まずパネルに入れるかを判定し、次にリソースの各操作(閲覧・作成・編集・削除)ごとに許可・拒否を判断します。


パネルへのアクセス制限

FilamentUser インターフェースで制御

<?php
// 📁 app/Models/User.php

namespace App\Models;

use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements FilamentUser
{
    public function canAccessPanel(Panel $panel): bool
    {
        // 方法1: 管理者メールドメインのみ許可
        return str_ends_with($this->email, '@mycompany.com');
    }
}
// 方法2: is_admin フラグで制御
public function canAccessPanel(Panel $panel): bool
{
    return (bool) $this->is_admin;
}

// 方法3: ロールで制御(spatie/laravel-permission などと組み合わせ)
public function canAccessPanel(Panel $panel): bool
{
    return $this->hasRole(['admin', 'editor', 'viewer']);
}

// 方法4: 複数パネルを運用する場合はパネルIDで分岐
public function canAccessPanel(Panel $panel): bool
{
    return match ($panel->getId()) {
        'admin'  => $this->hasRole('admin'),
        'staff'  => $this->hasRole(['admin', 'staff']),
        default  => false,
    };
}

PanelProvider でミドルウェアを追加

// 📁 app/Providers/Filament/AdminPanelProvider.php

->authMiddleware([
    Authenticate::class,
    // カスタムミドルウェアを追加
    \App\Http\Middleware\EnsureEmailIsVerified::class,
])

リソース単位のアクセス制御

Resource クラスに直接定義する方法

<?php
// 📁 app/Filament/Resources/PostResource.php

class PostResource extends Resource
{
    // リソース全体を非表示にする(サイドバーから消す)
    public static function canViewAny(): bool
    {
        return auth()->user()?->hasPermissionTo('view posts') ?? false;
    }

    // 新規作成を禁止
    public static function canCreate(): bool
    {
        return auth()->user()?->hasPermissionTo('create posts') ?? false;
    }

    // 特定レコードの編集を制御
    public static function canEdit(\Illuminate\Database\Eloquent\Model $record): bool
    {
        $user = auth()->user();
        // 管理者か、自分の投稿なら編集可能
        return $user?->hasRole('admin') || $record->user_id === $user?->id;
    }

    // 特定レコードの削除を制御
    public static function canDelete(\Illuminate\Database\Eloquent\Model $record): bool
    {
        return auth()->user()?->hasRole('admin') ?? false;
    }

    // 詳細表示の制御
    public static function canView(\Illuminate\Database\Eloquent\Model $record): bool
    {
        return true; // 全員に許可
    }
}

Laravel Policy との連携

Laravelのポリシーを作成して、Filamentに自動適用させる方法です。

ポリシーを生成

php artisan make:policy PostPolicy --model=Post
<?php
// 📁 app/Policies/PostPolicy.php

namespace App\Policies;

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

class PostPolicy
{
    use HandlesAuthorization;

    // リスト表示(管理画面の一覧)
    public function viewAny(User $user): bool
    {
        return $user->hasPermissionTo('view any posts');
    }

    // 個別表示
    public function view(User $user, Post $post): bool
    {
        return $user->hasRole('admin') || $post->user_id === $user->id;
    }

    // 作成
    public function create(User $user): bool
    {
        return $user->hasPermissionTo('create posts');
    }

    // 更新
    public function update(User $user, Post $post): bool
    {
        // 管理者か、下書きの自分の投稿のみ編集可
        return $user->hasRole('admin') ||
               ($post->user_id === $user->id && ! $post->is_published);
    }

    // 削除
    public function delete(User $user, Post $post): bool
    {
        return $user->hasRole('admin');
    }

    // ソフトデリートの復元
    public function restore(User $user, Post $post): bool
    {
        return $user->hasRole('admin');
    }

    // 完全削除
    public function forceDelete(User $user, Post $post): bool
    {
        return $user->hasRole('admin');
    }
}

ポリシーをAuthServiceProviderに登録

Laravel 11以降は AppServiceProvider で登録します。

<?php
// 📁 app/Providers/AppServiceProvider.php

use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    Gate::policy(\App\Models\Post::class, \App\Policies\PostPolicy::class);
}

Filamentでポリシーを自動適用する

PostResource$model が設定されている場合、Filamentは自動でポリシーを検出します。ただし canCreate() などを上書きしていない場合に限ります。

明示的にポリシーを使う設定:

// 📁 app/Providers/Filament/AdminPanelProvider.php
->authorizationGuard('web') // 使用するガードを指定

ロールベースのアクセス制御

spatie/laravel-permission を使ったロールベース制御の実装例です。

composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate

ロールとパーミッションのシード

<?php
// 📁 database/seeders/RoleSeeder.php

use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

class RoleSeeder extends Seeder
{
    public function run(): void
    {
        // パーミッションを作成
        $permissions = [
            'view posts', 'create posts', 'edit posts', 'delete posts',
            'view users', 'create users', 'edit users', 'delete users',
        ];

        foreach ($permissions as $permission) {
            Permission::firstOrCreate(['name' => $permission]);
        }

        // ロールを作成してパーミッションを割り当て
        $admin = Role::firstOrCreate(['name' => 'admin']);
        $admin->syncPermissions($permissions); // 全権限

        $editor = Role::firstOrCreate(['name' => 'editor']);
        $editor->syncPermissions([
            'view posts', 'create posts', 'edit posts',
        ]);

        $viewer = Role::firstOrCreate(['name' => 'viewer']);
        $viewer->syncPermissions(['view posts']);
    }
}

リソースにパーミッションチェックを組み込む

class PostResource extends Resource
{
    public static function canViewAny(): bool
    {
        return auth()->user()?->hasPermissionTo('view posts') ?? false;
    }

    public static function canCreate(): bool
    {
        return auth()->user()?->hasPermissionTo('create posts') ?? false;
    }

    public static function canEdit(\Illuminate\Database\Eloquent\Model $record): bool
    {
        return auth()->user()?->hasPermissionTo('edit posts') ?? false;
    }

    public static function canDelete(\Illuminate\Database\Eloquent\Model $record): bool
    {
        return auth()->user()?->hasPermissionTo('delete posts') ?? false;
    }
}

ナビゲーショングループごとに表示を制御します。

// 📁 app/Filament/Resources/UserResource.php

protected static ?string $navigationGroup = 'ユーザー管理';

// この NavigationGroup が表示されるかどうか
public static function canViewAny(): bool
{
    return auth()->user()?->hasRole('admin') ?? false;
}

PanelProvider でナビゲーショングループを明示的に定義

// 📁 app/Providers/Filament/AdminPanelProvider.php

use Filament\Navigation\NavigationGroup;

->navigationGroups([
    NavigationGroup::make()
        ->label('コンテンツ管理')
        ->icon('heroicon-o-document-text'),

    NavigationGroup::make()
        ->label('ユーザー管理')
        ->icon('heroicon-o-users')
        ->collapsed(), // デフォルトで折りたたむ
])

パネル全体への認証ミドルウェア設定

// 📁 app/Providers/Filament/AdminPanelProvider.php

->authMiddleware([
    // ログインしていない場合はログインページへリダイレクト
    \Filament\Http\Middleware\Authenticate::class,
])

// ログインページのルートをカスタマイズ
->login(\App\Filament\Pages\Auth\Login::class)

// メール確認を強制
->requiresEmailVerification()

カスタムログインページの例

<?php
// 📁 app/Filament/Pages/Auth/Login.php

namespace App\Filament\Pages\Auth;

use Filament\Pages\Auth\Login as BaseLogin;

class Login extends BaseLogin
{
    // ログインフォームにIP制限を追加する例
    public function authenticate(): ?LoginResponse
    {
        $allowedIps = config('admin.allowed_ips', []);

        if (! empty($allowedIps) && ! in_array(request()->ip(), $allowedIps)) {
            $this->addError('form', 'このIPアドレスからはアクセスできません。');
            return null;
        }

        return parent::authenticate();
    }
}

まとめ

  • canAccessPanel(Panel $panel) でユーザーごとにパネルへのアクセスを制御
  • リソースには canViewAny() / canCreate() / canEdit($record) / canDelete($record) を定義
  • Laravel の Policy を作成・登録するとFilamentが自動で適用する
  • spatie/laravel-permission でロール・パーミッション管理を実装できる
  • NavigationGroup にもアクセス制御を設定可能
  • ->authMiddleware([]) でパネル全体に認証ミドルウェアを適用

次回はグローバル検索とナビゲーションのカスタマイズを学びます。