Home Arrow Icon Knowledge base Arrow Icon Global Arrow Icon How can I test the integration of Spatie Permissions with Laravel Passport


How can I test the integration of Spatie Permissions with Laravel Passport


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.