Permissions & Authorization

How to create permissions, policies, and authorization checks in LaraDashboard modules using PermissionService, Spatie Laravel Permission, and Laravel policies.

Permissions & Authorization

LaraDashboard uses Spatie Laravel Permission for role-based access control. Modules create their own permissions via migrations and enforce them through Laravel policies.

Creating Permissions

PermissionService

Use PermissionService to create CRUD permissions in migrations:

<?php

declare(strict_types=1);

use App\Services\PermissionService;
use Illuminate\Database\Migrations\Migration;

return new class extends Migration
{
    public function up(): void
    {
        // Creates: contact.view, contact.create, contact.edit, contact.delete
        PermissionService::createCrudPermissions('contact', 'Contact');
    }

    public function down(): void
    {
        PermissionService::deleteCrudPermissions('contact');
    }
};

This creates four permissions following the {resource_snake}.{action} convention:

Permission Description
contact.view View contacts list and details
contact.create Create new contacts
contact.edit Edit existing contacts
contact.delete Delete contacts

Custom Permissions

For non-CRUD permissions, create them directly:

use Spatie\Permission\Models\Permission;

Permission::create(['name' => 'contact.export', 'guard_name' => 'web']);
Permission::create(['name' => 'contact.import', 'guard_name' => 'web']);

Permission Naming Convention

Always use {resource_snake}.{action}:

contact.view
contact.create
contact.edit
contact.delete
deal.view
deal.create
ticket.assign
campaign.send

Creating Policies

Generate Policy

php artisan make:policy ContactPolicy --model=Contact

Or create manually in modules/{Module}/app/Policies/:

<?php

declare(strict_types=1);

namespace Modules\Crm\Policies;

use App\Models\User;
use Modules\Crm\Models\Contact;

class ContactPolicy
{
    public function viewAny(User $user): bool
    {
        return $user->hasPermissionTo('contact.view');
    }

    public function view(User $user, Contact $contact): bool
    {
        return $user->hasPermissionTo('contact.view');
    }

    public function create(User $user): bool
    {
        return $user->hasPermissionTo('contact.create');
    }

    public function update(User $user, Contact $contact): bool
    {
        return $user->hasPermissionTo('contact.edit');
    }

    public function delete(User $user, Contact $contact): bool
    {
        return $user->hasPermissionTo('contact.delete');
    }
}

Register Policy

Register the policy in your module's ServiceProvider:

use Illuminate\Support\Facades\Gate;
use Modules\Crm\Models\Contact;
use Modules\Crm\Policies\ContactPolicy;

public function boot(): void
{
    Gate::policy(Contact::class, ContactPolicy::class);
}

Enforcing Authorization

In Controllers

Use $this->authorize() in every controller action:

public function index(): Renderable
{
    $this->authorize('viewAny', Contact::class);
    // ...
}

public function show(int $id): Renderable
{
    $contact = Contact::findOrFail($id);
    $this->authorize('view', $contact);
    // ...
}

public function store(ContactRequest $request): RedirectResponse
{
    $this->authorize('create', Contact::class);
    // ...
}

public function update(ContactRequest $request, int $id): RedirectResponse
{
    $contact = Contact::findOrFail($id);
    $this->authorize('update', $contact);
    // ...
}

public function destroy(int $id): RedirectResponse
{
    $contact = Contact::findOrFail($id);
    $this->authorize('delete', $contact);
    // ...
}

In Blade Views

@can('create', \Modules\Crm\Models\Contact::class)
    <a href="{{ route('admin.crm.contacts.create') }}" class="btn btn-primary">
        Add Contact
    </a>
@endcan

@can('delete', $contact)
    <button class="btn btn-danger btn-sm">Delete</button>
@endcan

In Menu Service

Menu items are hidden based on permissions:

(new AdminMenuItem())->setAttributes([
    'label' => __('Contacts'),
    'route' => route('admin.crm.contacts.index'),
    'permissions' => ['contact.view'],  // Hidden if user lacks this
]);

Direct Permission Checks

// Check permission
if ($user->hasPermissionTo('contact.export')) {
    // ...
}

// Check role
if ($user->hasRole('super-admin')) {
    // ...
}

// Abort if unauthorized
abort_unless(auth()->user()->can('contact.edit'), 403);

Superadmin Role

The super-admin role bypasses all permission checks. This is handled automatically by Spatie's SuperAdminRole gate.

When creating new permissions, you may need to assign them to the superadmin role:

use App\Services\PermissionService;

// PermissionService::createCrudPermissions automatically assigns to super-admin
PermissionService::createCrudPermissions('contact', 'Contact');

Module Permission Flow

1. Migration creates permissions via PermissionService
2. Policy checks permissions with hasPermissionTo()
3. Policy registered in ServiceProvider via Gate::policy()
4. Controller uses $this->authorize() on every action
5. Menu items hidden via 'permissions' array
6. Blade uses @can / @cannot directives

Next Steps

/