Skip to content

Instantly share code, notes, and snippets.

@atomjoy
Last active April 29, 2025 11:49
Show Gist options
  • Select an option

  • Save atomjoy/e5e2e826e6cc233752aaf4b49dc2bd0e to your computer and use it in GitHub Desktop.

Select an option

Save atomjoy/e5e2e826e6cc233752aaf4b49dc2bd0e to your computer and use it in GitHub Desktop.
Laravel Spatie Permissions

Laravel Spatie Permissions

How to run multi guard authentication with permissions in Laravel application.

Cmd project

cd /
mkdir www
cd www

composer create-project laravel/laravel wow.test

code wow.test

Install packages

composer require spatie/laravel-permission

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

php artisan optimize:clear

php artisan migrate:fresh --seed

Create provider

php artisan make:provider PermissionsProvider

Update provider class

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Spatie\Permission\Middleware\RoleMiddleware;
use Spatie\Permission\Middleware\PermissionMiddleware;
use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;

class PermissionProvider extends ServiceProvider
{
    /**
     * Register services.
    */
    public function register(): void
    {
        // Spatie permissions
        $this->app['router']->aliasMiddleware('role', RoleMiddleware::class);
        $this->app['router']->aliasMiddleware('permission', PermissionMiddleware::class);
        $this->app['router']->aliasMiddleware('role_or_permission', RoleOrPermissionMiddleware::class);
    }

    /**
     * Bootstrap services.
    */
    public function boot(): void
    {
        // Implicitly grant "Super Admin" role all permissions
        // This works in the app by using gate-related
        // functions like auth()->user->can() and @can()
        Gate::before(function ($user, $ability) {
            return $user->hasRole('super_admin') ? true : null;
        });
    }
}

Create triat

<?php

namespace App\Contracts\Spatie;

trait HasPermission
{
    /**
     * Append user all permissions  (roles + direct)
     *
     * @var array
     */
    protected $appends = ['permission'];

    /**
     * Spatie get all user permissions (direct + roles)
     */
    public function getPermissionAttribute()
    {
        return $this->getAllPermissions();
    }

    /**
     * Spatie roles with permissions (sample only)
    */
    public function roles_permissions()
    {
        return $this->roles()->with(['permissions' => function ($q) {
            $q->select('id', 'name', 'guard_name');
        }]);
    }
}

Update User, Admin

use Spatie\Permission\Traits\HasRoles;
use App\Contracts\Spatie\HasPermission;

# The User, Admin model requires this trait
use HasRoles;
use HasPermission;

protected function getDefaultGuardName(): string { return 'web'; }

Create roles, permissions

Docs https://spatie.be/docs/laravel-permission/v6/basic-usage/basic-usage

// Get roles
$role = Role::findByName('user');
$role = Role::findByName('admin');

// Set roles, permissions
$role->givePermissionTo($permission);
$permission->assignRole($role);

// Create permissions
$permissions = [
    'post_create',
    'post_delete',
    'post_update',
    'post_show',
];

foreach ($permissions as $permission) {
    // Admin guard
    Permission::create([
        'name' => $permission,
        'guard_name' => 'admin',
    ]);
}

// Super Admin role
$superadmin = Role::create([
   'name' => 'super_admin',
   'guard_name' => 'admin'
]);

// Add permissions to super_admin role
$superadmin->givePermissionTo([
    ...$post,
]);

// Admin role
$admin = Role::create([
    'name' => 'admin',
    'guard_name' => 'web'
]);

// Add permissions to admin role
$admin->givePermissionTo([
    ...$post,
]);

// Writer role
$writer = Role::create([
    'name' => 'writer',
    'guard_name' => 'web'
]);

// Add permissions to writer role
$writer->givePermissionTo([
    ...$post,
]);

// User role (guard web)
$user = Role::create([
    'name' => 'user',
    'guard_name' => 'web'
]);

// Add permissions to role
$user->givePermissionTo([
    ...$post,
]);

Check roles and permissions

    // User guard
    $role = app(Role::class)->findOrCreate(RolesEnum::WRITER->value, 'web');
    $role = Role::create(['guard_name' => 'web', 'name' => 'writer']);
    
    // Admin guard
    $role = app(Role::class)->findOrCreate(RolesEnum::WRITER->value, 'admin');    
    $role = Role::create(['guard_name' => 'admin', 'name' => 'writer']);

    // Permissions
    $direct_permission = app(Permission::class)->findOrCreate(PermissionsEnum::ARTICLE_DELETE->value, 'admin');
    $direct_permission = Permission::create(['guard_name' => 'admin', 'name' => 'article_delete']);

    // Add, find role
    $user->assignRole('writer');
    $role = Role::findByName('writer', 'web');
    $role = Role::findByName('writer', 'admin');
    
    // Roles
	$user->assignRole(RolesEnum::WRITER);
	$user->removeRole(RolesEnum::WRITER);
    
    // Permissions
    $role->givePermissionTo(PermissionsEnum::ARTICLE_VIEW);
    $role->revokePermissionTo(PermissionsEnum::ARTICLE_VIEW);

    // Roles
    echo $user->hasRole('super_admin') ? 'YES' : 'NO';
    echo $user->roles->pluck('name');

    // Permissions from roles
    echo $user->getPermissionsViaRoles()->pluck('name');

    // Only direct permissions not from roles
    $user->givePermissionsTo('article_update');
    // Get
    echo $user->permissions->pluck('name');
    echo $user->getDirectPermissions()->pluck('name');
    // Check
    echo $user->can('edit_articles') ? 'Yes' : 'No';
    echo $user->hasPermissionTo('edit_articles'); ? 'Yes' : 'No';
    echo $user->hasAnyPermission(['edit_articles','delete_article']); ? 'Yes' : 'No';
    echo $user->hasAllPermissions(['edit_articles','delete_article']); ? 'Yes' : 'No';

    // Check permissions
    $user->hasPermissionTo(PermissionsEnum::VIEWPOSTS->value);
    // When calling Gate features, such as Model Policies, etc, prior to Laravel v11.23.0
    $user->can(PermissionsEnum::VIEWPOSTS->value);

Create routes

// User routes
Route::prefix('web/api')->name('web.api')->middleware([
    'web',
])->group(function () {
    Route::get('/posts', function () {
        return response()->json([
            'message' => 'Display posts here',
            'guard' => 'web',
        ]);
    });
    
    // Private (guard web)
    Route::middleware([
        'auth:web',
        'role:writer,web'
    ])->group(function () {
        // Only super_admin
    });
});

// Admin api
Route::prefix('web/api/admin')->name('web.api.admin')->middleware([
    'web',
])->group(function () {
    // Public routes
    # Route::post('/login', [AdminLoginController::class, 'index'])->name('login');
    Route::post('/login', function () {
        // Sample login
        $credentials = request()->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);
        if (Auth::guard('admin')->attempt($credentials, request()->filled('remember'))) {
            request()->session()->regenerate();
            return response()->json([
                'message' => 'Admin logged',
                'guard' => 'admin',
            ]);
        } else {
            return response()->json([
                'message' => 'Admin invalid credentials',
                'guard' => 'admin',
            ], 422);
        }
    });

    // Private (guard admin)
    Route::middleware([
        'auth:admin',
        'role:super_admin,admin'
    ])->group(function () {
        // Only super_admin
    });

    Route::middleware([
        'auth:admin',
        'role:super_admin|admin,admin'
    ])->group(function () {
        // Only super_admin and admin
    });

    Route::middleware([
        'auth:admin',
        'role:super_admin|admin|writer,admin'
    ])->group(function () {

        Route::get('/logged', function () {
            return response()->json([
                'message' => 'Admin logged',
                'guard' => 'admin',
                'id' => Auth::guard('admin')->id(),
            ]);
        });

        Route::get('/posts', function () {
            return response()->json([
                'message' => 'Display posts here',
                'guard' => 'admin',
            ]);
        });

        // All routes
        // Route::resource('/posts', PostController::class);
    });
});

Create Posts model (optional)

# Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests for Route::resource
php artisan make:model Post --all --resource

# Then routes
Route::resource('/posts', PostController::class);

# Photo example
# php artisan make:controller PhotoController --model=Photo --resource --requests

Tests

Auth users

// Use User for request with guard
$this->actingAs($user, 'admin');

// Check is user logged with guard
Auth::guard('admin')->check();

// Logout user with guard
Auth::shouldUse('admin');
Auth::logout();

// Logout user with guard
Auth::guard('admin')->logout();

Run test

php artisan cache:clear
php artisan test --filter=PermissionsTest
<?php
namespace App\Enums;
enum PermissionsEnum: string
{
case ALLOW_LOGIN = 'allow_login';
case MANAGE_ROLES = 'manage_roles';
case MANAGE_USERS = 'manage_users';
case MANAGE_SETTINGS = 'manage_settings';
case ARTICLE_VIEW = 'article_view';
case ARTICLE_CREATE = 'article_create';
case ARTICLE_UPDATE = 'article_update';
case ARTICLE_DELETE = 'article_delete';
}
<?php
namespace App\Enums;
enum RolesEnum: string
{
case WRITER = 'writer';
case ADMIN = 'admin';
case SUPERADMIN = 'super_admin';
public function label(): string
{
return match ($this) {
static::WRITER => 'Writers',
static::ADMIN => 'Admins',
static::SUPERADMIN => 'Super Admins',
};
}
}
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__ . '/../routes/web.php',
commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
// Json response
$exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
if ($request->is('web/api/*')) {
return true;
}
return $request->expectsJson();
});
// Route model not found error
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('web/api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
})->create();
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Spatie\Permission\Middleware\RoleMiddleware;
use Spatie\Permission\Middleware\PermissionMiddleware;
use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;
class PermissionProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
// Spatie permissions
$this->app['router']->aliasMiddleware('role', RoleMiddleware::class);
$this->app['router']->aliasMiddleware('permission', PermissionMiddleware::class);
$this->app['router']->aliasMiddleware('role_or_permission', RoleOrPermissionMiddleware::class);
}
/**
* Bootstrap services.
*/
public function boot(): void
{
// Implicitly grant "Super Admin" role all permissions
// This works in the app by using gate-related
// functions like auth()->user->can() and @can()
Gate::before(function ($user, $ability) {
return $user->hasRole('super_admin') ? true : null;
});
}
}
<?php
namespace Tests\Feature;
use App\Models\User;
use Database\Seeders\TestSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PermissionsTest extends TestCase
{
use RefreshDatabase;
/**
* A basic test example.
*/
public function test_publish_error(): void
{
$response = $this->getJson('/web/api/publish');
$response->assertStatus(401);
}
/**
* A basic test example.
*/
public function test_user_roles(): void
{
$this->seed(TestSeeder::class);
$user = User::first();
$this->actingAs($user);
$response = $this->getJson('/web/api/roles');
$response->assertStatus(200);
$response->assertJson([
'user' => [
"id" => 1,
'permission' => [
[
"id" => 1,
"name" => "article_create",
"guard_name" => "web"
],
[
"id" => 2,
"name" => "article_delete",
"guard_name" => "web"
],
],
'roles' => [
[
"id" => 1,
"name" => "writer",
"guard_name" => "web",
'permissions' => [
[
"id" => 1,
"name" => "article_create",
"guard_name" => "web"
],
],
]
],
]
]);
}
/**
* A basic test example.
*/
public function test_publish_ok(): void
{
$this->seed(TestSeeder::class);
$user = User::first();
$user->givePermissionTo('article_delete');
$this->actingAs($user);
$response = $this->getJson('/web/api/publish');
$response->assertStatus(200);
$response = $this->getJson('/web/api/delete');
$response->assertStatus(200);
$user->revokePermissionTo('article_delete');
$response = $this->getJson('/web/api/delete');
$response->assertStatus(403);
}
}
<?php
namespace Database\Seeders;
use App\Enums\PermissionsEnum;
use App\Enums\RolesEnum;
use App\Models\User;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class TestSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
// User::factory(10)->create();
$user = User::factory()->create([
'name' => 'Alex User',
'email' => '[email protected]',
]);
$this->permissions($user);
}
function permissions($user)
{
$role = app(Role::class)->findOrCreate(RolesEnum::WRITER->value, 'web');
// $role = Role::create(['name' => 'writer', 'guard_name' => 'web']);
$permission = app(Permission::class)->findOrCreate(PermissionsEnum::ARTICLE_CREATE->value, 'web');
// $permission = Permission::create(['name' => 'article_create', 'guard_name' => 'web']);
$direct_permission = app(Permission::class)->findOrCreate(PermissionsEnum::ARTICLE_DELETE->value, 'web');
// $direct_permission = Permission::create(['name' => 'article_delete', 'guard_name' => 'web']);
$role->givePermissionTo($permission);
// $permission->assignRole($role);
// Add role permissions
$user->assignRole($role);
// Add direct permissions
$user->givePermissionTo($direct_permission);
}
}
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
use HasRoles;
/**
* Append user all permissions
*
* @var array
*/
protected $appends = ['permission'];
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
/**
* Spatie get all user permissions (direct + roles)
*/
public function getPermissionAttribute()
{
return $this->getAllPermissions();
}
}
<?php
use Illuminate\Support\Facades\Route;
use App\Models\User;
Route::get('/', function () {
return view('welcome');
});
Route::prefix('web/api')->name('web.api')->middleware([
'web',
])->group(function () {
// Public
// Route::post('/login', [AdminLoginController::class, 'index'])->name('login');
Route::get('/roles', function () {
return response()->json([
'user' => User::first()
]);
});
// Private admin guard
Route::middleware([
'auth',
// 'role:writer|admin',
'permission:article_create|article_edit',
// 'auth:admin',
// 'role:writer|worker|admin|super_admin,admin',
])->group(function () {
Route::get('publish', function () {
return response()->json(['message' => 'Publish Works']);
});
});
Route::middleware([
'auth',
// 'role:writer|admin',
'permission:article_delete',
// 'auth:admin',
// 'role:writer|worker|admin|super_admin,admin',
])->group(function () {
Route::get('delete', function () {
return response()->json(['message' => 'Delete Works']);
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment