Testing the integration of Spatie Permissions with Laravel Passport involves several steps that ensure the permission and role management system works seamlessly with OAuth2 authentication provided by Passport. Below is an in-depth guide covering installation, setup, integration, and testing strategies.
Initial Setup of Laravel, Passport, and Spatie Permissions
1. Install Laravel**
Start with a fresh Laravel application or an existing one.
2. Install Laravel Passport**
Laravel Passport provides a full OAuth2 server implementation.
bash
composer require laravel/passport
php artisan migrate
php artisan passport:install
Configure Passport in `config/auth.php` by setting the API guard driver to `passport`:
php
'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
Register Passport routes in `App\Providers\AuthServiceProvider.php`:
php
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
3. Install Spatie Laravel Permission Package**
bash
composer require spatie/laravel-permission
Publish config and migration files:
bash
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
4. Add HasRoles Trait**
Add the `HasRoles` trait in the `User` model:
php
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
// ...
}
Configuring Spatie Permissions with Laravel Passport
1. Guard Configuration**
Ensure that the guard in Spatie configuration matches Passport's guard (`api` by default):
In `config/permission.php`:
php
'defaults' => [
'guard' => 'api',
],
Alternatively, specify the guard explicitly in your user model or when assigning roles/permissions.
2. Extending Passport Client Model (Optional)**
If using client credentials grant and roles for clients, extend Passport's Client model with HasRoles trait:
php
use Spatie\Permission\Traits\HasRoles;
use Laravel\Passport\Client as BaseClient;
class Client extends BaseClient
{
use HasRoles;
public $guard_name = 'api';
}
Then tell Passport to use this model in `AuthServiceProvider`:
php
Passport::useClientModel(Client::class);
Configure `'use_passport_client_credentials' => true` in `config/permission.php` to enable checks on clients.
3. Role and Permission Creation & Assignment**
Create roles and permissions, then assign permissions to roles and roles to users:
php
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
$role = Role::create(['name' => 'admin']);
$permission = Permission::create(['name' => 'manage users']);
$role->givePermissionTo($permission);
$user->assignRole('admin');
Integrating Permissions Checks within Passport Authenticated API
1. Generating Access Tokens**
After validating credentials, issue Passport tokens:
php
if (Auth::attempt($credentials)) {
$user = Auth::user();
if ($user->hasRole('admin')) {
$token = $user->createToken('AppToken')->accessToken;
return response()->json(['token' => $token]);
} else {
return response()->json(['error' => 'Unauthorized'], 403);
}
}
2. Middleware for Role/Permission Checks**
Protect your routes by checking roles or permissions:
php
Route::middleware(['auth:api', 'role:admin'])->group(function () {
Route::get('/admin/dashboard', [AdminController::class, 'index']);
});
You may use Spatie's middleware `role` or `permission` middleware as provided by the package, configured in `app/Http/Kernel.php`.
3. Authorization Gates and Policies**
Define gates or policies to leverage permission checks:
php
Gate::define('manage-users', function ($user) {
return $user->hasPermissionTo('manage users');
});
Writing Tests for Spatie Permissions with Passport
1. Prepare Testing Environment**
Use the `RefreshDatabase` trait to reset the database:
php
use Illuminate\Foundation\Testing\RefreshDatabase;
class PermissionPassportTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// Run seeders or create roles/permissions here
$this->artisan('passport:install');
$this->seed(RolePermissionSeeder::class);
}
}
2. Seed Roles and Permissions**
Create a seeder (`RolePermissionSeeder`) to insert default data:
php
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class RolePermissionSeeder extends Seeder
{
public function run()
{
$role = Role::create(['name' => 'admin']);
$permission = Permission::create(['name' => 'manage users']);
$role->givePermissionTo($permission);
}
}
3. Create Users for Testing**
Create different users with different roles and permissions using factories or manually:
php
$admin = User::factory()->create();
$admin->assignRole('admin');
$regularUser = User::factory()->create();
4. Testing Authentication and Role-based Access**
Use Passport's token creation and HTTP methods to call protected endpoints:
php
public function test_admin_access()
{
$admin = User::factory()->create();
$admin->assignRole('admin');
$token = $admin->createToken('TestToken')->accessToken;
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $token,
])->getJson('/api/admin/dashboard');
$response->assertStatus(200);
}
public function test_non_admin_access_denied()
{
$user = User::factory()->create();
$token = $user->createToken('TestToken')->accessToken;
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $token,
])->getJson('/api/admin/dashboard');
$response->assertStatus(403);
}
5. Testing Permissions Directly**
Check if a user has a specific permission:
php
public function test_user_permission()
{
$user = User::factory()->create();
$user->givePermissionTo('manage users');
$this->assertTrue($user->hasPermissionTo('manage users'));
}
6. Test Middleware Usage**
Write tests to verify middleware blocks or allows access based on roles and permissions.
Handling Edge Cases and Best Practices in Testing
- Seeding vs Manual Setup: Consider seeding roles and permissions globally for tests, or set them up per test for isolation.
- Database Transactions: Use transactions to roll back database state to keep tests isolated.
- Custom Guards and Multiple Auth: Specify guard name explicitly if multi-auth or custom guards used.
- Token Expiry and Revocation: Write tests to check token expiration and revocation effects on access and permissions.
- Policy and Gate Testing: Test gates and policies separately to ensure permission enforcement logic correctness.
- Testing with Client Credentials Grant: If using client credentials, extend Passport's client model with the HasRoles trait and test roles assigned to clients.
Sample Test Case Combining Passport and Spatie
php
class PassportSpatieIntegrationTest extends TestCase
{
use RefreshDatabase;
public function setUp(): void
{
parent::setUp();
$this->artisan('passport:install');
$this->seed(RolePermissionSeeder::class);
}
public function test_admin_can_access_protected_route()
{
$admin = User::factory()->create();
$admin->assignRole('admin');
$token = $admin->createToken('TestToken')->accessToken;
$response = $this->withHeaders([
'Authorization' => "Bearer $token",
])->getJson('/api/admin/dashboard');
$response->assertStatus(200);
}
public function test_user_without_role_is_denied()
{
$user = User::factory()->create();
$token = $user->createToken('TestToken')->accessToken;
$response = $this->withHeaders([
'Authorization' => "Bearer $token",
])->getJson('/api/admin/dashboard');
$response->assertStatus(403);
}
}
Conclusion
Testing the integration of Spatie Permissions with Laravel Passport revolves around verifying that the OAuth2 token authentication works correctly with the role and permission checks provided by Spatie's package. The main points are to ensure correct setup of guards, proper role and permission assignment, middleware guarding, and thorough testing of permission enforcement using Laravel's testing features combined with Passport token generation.
This includes steps from application setup, creating roles and permissions, securing API endpoints, to writing comprehensive test cases that confirm the system's behavior under various authenticated user scenarios.
This approach confirms that only authorized users with relevant roles and permissions can access protected Passport-authenticated routes, thereby providing a robust security mechanism for Laravel API applications.