Skip to content

Multi-Guard Support

Warden fully supports Laravel’s multiple authentication guards, allowing you to maintain separate permission systems for different parts of your application (web, API, RPC, etc.).

Set your default guard in config/warden.php:

'guard' => env('WARDEN_GUARD', 'web'),

Use Warden::guard() to create guard-scoped instances:

$webWarden = Warden::guard('web');
$apiWarden = Warden::guard('api');
$rpcWarden = Warden::guard('rpc');

Roles are isolated by guard - the same role name can exist across different guards:

// Web guard admin
Warden::guard('web')->assign('admin')->to($user);
// API guard admin (separate role)
Warden::guard('api')->assign('admin')->to($user);
// RPC guard admin (separate role)
Warden::guard('rpc')->assign('admin')->to($user);

Abilities are also guard-scoped:

// Web permissions
Warden::guard('web')->allow($user)->to('manage-posts');
Warden::guard('web')->allow('editor')->to('edit-posts');
// API permissions
Warden::guard('api')->allow($user)->to('read-data');
Warden::guard('api')->allow($user)->to('write-data');
// RPC permissions
Warden::guard('rpc')->allow($user)->to('call-methods');

Forbid abilities for specific guards:

Warden::guard('web')->forbid($user)->to('delete-posts');
Warden::guard('api')->allow($user)->to('delete-posts'); // Still allowed via API

When you don’t specify a guard, Warden uses the configured default (typically web):

// Uses default guard from config
Warden::assign('admin')->to($user);
Warden::allow($user)->to('manage-posts');

Guards are completely isolated from each other:

// Create web admin
Warden::guard('web')->assign('admin')->to($user);
// Query only web roles
$webRoles = Models::role()->forGuard('web')->get();
// Query only API roles
$apiRoles = Models::role()->forGuard('api')->get();
// No cross-contamination
expect($webRoles->contains('name', 'admin'))->toBeTrue();
expect($apiRoles->contains('name', 'admin'))->toBeFalse();

Spatie’s guard_name is automatically preserved:

$migrator = new SpatieMigrator(User::class);
$migrator->migrate();
// Roles maintain their original guard
$webAdmin = Models::role()
->where('name', 'admin')
->where('guard_name', 'web')
->first();
$apiAdmin = Models::role()
->where('name', 'admin')
->where('guard_name', 'api')
->first();

Bouncer doesn’t have guard support, so specify the target guard:

// Default to 'web'
$migrator = new BouncerMigrator(User::class);
// Or specify a guard
$apiMigrator = new BouncerMigrator(User::class, 'migration', 'api');
$rpcMigrator = new BouncerMigrator(User::class, 'migration', 'rpc');
// Web admin with full permissions
$webWarden = Warden::guard('web');
$webWarden->assign('admin')->to($user);
$webWarden->allow('admin')->to('*');
// API with read-only access
$apiWarden = Warden::guard('api');
$apiWarden->assign('api-consumer')->to($user);
$apiWarden->allow('api-consumer')->to('read-data');

Combine guards with multi-tenancy:

Warden::guard('web')
->assign('tenant-admin')
->within($organization)
->to($user);
Warden::guard('api')
->allow($user)
->within($organization)
->to('access-tenant-data');

Separate permissions for internal RPC calls:

Warden::guard('rpc')->allow($serviceAccount)->to('internal-methods');
Warden::guard('rpc')->forbid($publicUser)->to('internal-methods');
// All web roles
$webRoles = Models::role()->forGuard('web')->get();
// Specific role in guard
$webAdmin = Models::role()
->where('name', 'admin')
->where('guard_name', 'web')
->first();
// All API abilities
$apiAbilities = Models::ability()->forGuard('api')->get();
// Specific ability in guard
$apiWrite = Models::ability()
->where('name', 'write-data')
->where('guard_name', 'api')
->first();
  1. Explicit Guards in Production: Always use Warden::guard('name') in production code to avoid confusion
  2. Consistent Naming: Use descriptive guard names like web, api, admin-api, rpc
  3. Document Guard Usage: Clearly document which guards are used where in your application
  4. Test Guard Isolation: Write tests to ensure guards don’t leak permissions
  5. Migration Planning: Plan guard assignment when migrating from packages without guard support

The guard_name column is added to both the roles and abilities tables:

Schema::table('warden_roles', function (Blueprint $table) {
$table->string('guard_name')->default('web');
});
Schema::table('warden_abilities', function (Blueprint $table) {
$table->string('guard_name')->default('web');
});

Unique constraints ensure role names are unique per guard:

$table->unique(['name', 'guard_name', 'scope'], 'roles_name_guard_unique');