expertapps / laravel-abac
Enterprise-grade Attribute-Based Access Control (ABAC) using Policy Group and Action Matrix Strategy for Laravel.
Requires
- php: ^8.3
- illuminate/contracts: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^10.0 || ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- orchestra/testbench: ^8.0 || ^9.0 || ^10.0 || ^11.0
- pestphp/pest: ^2.34 || ^3.0 || ^4.0
- phpstan/phpstan: ^1.10 || ^2.0
- phpunit/phpunit: ^10.5 || ^11.0 || ^12.0
README
An enterprise-grade, ultra-fast Attribute-Based Access Control (ABAC) authorization engine for Laravel applications built by ExpertApps.
Unlike traditional Role-Based Access Control (RBAC) packages that rely on static database queries, laravel-abac evaluates authorization dynamically using runtime attributes across Subject, Resource, Action, and Environment parameters—executed via an in-memory $O(1)$ Policy Group & Action Matrix Strategy.
🎯 The Problem We Solve
In real-world enterprise applications, traditional RBAC (Role-Based Access Control) breaks down as authorization requirements become contextual and dynamic:
1. The "Role Explosion" Problem
In traditional RBAC, when access depends on conditions (e.g., "Medical Record Viewer in Cardiology during working hours"), developers are forced to invent endless artificial roles like Cardiology_Doctor_WorkingHours_ExportAllowed. This clutters database tables and makes role maintenance unsustainable.
2. Context Blindness
Standard Laravel Policies or RBAC packages evaluate permissions using static database records (User -> Roles -> Permissions). They struggle when permissions depend on real-time runtime parameters, such as:
- Environment: Can this user view the resource from an external IP address at 11:00 PM?
- Dynamic Limits: Can a user approve a refund up to $5,000, but require extra approval above $5,000?
- Resource Context: Can a doctor view a patient record only if they belong to the same medical department?
3. Database Bottlenecks
Standard database-backed permission systems execute N+1 database queries during complex request life cycles just to check permissions across nested models and UI elements.
⚡ How laravel-abac Solves It
laravel-abac shifts authorization from static database lookups to in-memory dynamic evaluation:
- Zero Database Queries: Authorization matrices are pre-registered upon application boot. Evaluation happens entirely in memory.
-
$O(1)$ Action Lookups: Exact array-map indexing routes
[ResourceClass][Action]directly to its target rule chain in constant time. - Dynamic Context Bag: Pass real-time environmental variables (IPs, monetary limits, request signatures) directly into evaluation context.
-
Fail-Closed Zero-Trust Model: Unmapped actions or unregistered resources automatically evaluate to
false(access denied), eliminating accidental security loopholes.
⚡ Key Architectural Features
- Zero Database Overhead: Pure in-memory Clean Architecture execution.
- $O(1)$ Action Matrix Indexing: Instant route matching based on target resources and actions.
- Fail-Closed Strategy: Denies access by default unless an explicit rule passes.
-
Fast-Pass Hook (
before): Provides global short-circuit logic (e.g., Super-Admin overrides). -
Native Laravel Ecosystem: Direct support for
Gate::allows(),$this->authorize(), Blade directives, and Artisan generators.
📋 Requirements
| Requirement | Supported Version |
|---|---|
| PHP | ^8.3 |
| Laravel Framework | ^10.0 | ^11.0 | ^12.0 | ^13.0 |
🚀 Installation
Install the package via Composer:
composer require expertapps/laravel-abac
Publish the package configuration:
php artisan vendor:publish --tag="abac-config"
⚙️ Configuration
The published config/abac.php file defines your policy group registry and fallback security behavior:
return [ /* |-------------------------------------------------------------------------- | Registered Policy Groups |-------------------------------------------------------------------------- | List of Policy Groups auto-registered into the AbacRuleRegistry | upon application bootstrap. */ 'policy_groups' => [ App\Abac\Policies\PatientRecordPolicyGroup::class, ], /* |-------------------------------------------------------------------------- | Strict Fail-Closed Policy |-------------------------------------------------------------------------- | When true, any unmapped resource or action evaluates strictly to false. */ 'fail_closed' => true, ];
🏁 Get Started Guide
Follow this step-by-step example to build your first ABAC authorization pipeline for a medical PatientRecord resource.
Step 1: Create Granular Rules
Granular rules implement single-responsibility access logic.
Generate rules using Artisan:
php artisan make:abac-rule DepartmentMatchRule php artisan make:abac-rule WorkingHoursRule php artisan make:abac-rule ExportFeeLimitRule
Rule 1: Check Department Alignment
namespace App\Abac\Rules; use ExpertApps\LaravelAbac\Contracts\AbacRuleInterface; use ExpertApps\LaravelAbac\Domain\AttributeContext; use App\Models\User; use App\Models\PatientRecord; final readonly class DepartmentMatchRule implements AbacRuleInterface { public function supports(AttributeContext $context): bool { return $context->subject instanceof User && $context->resource instanceof PatientRecord; } public function evaluate(AttributeContext $context): bool { /** @var User $user */ $user = $context->subject; /** @var PatientRecord $record */ $record = $context->resource; return $user->department === $record->department; } }
Rule 2: Evaluate Environmental Parameters (Working Hours)
namespace App\Abac\Rules; use ExpertApps\LaravelAbac\Contracts\AbacRuleInterface; use ExpertApps\LaravelAbac\Domain\AttributeContext; final readonly class WorkingHoursRule implements AbacRuleInterface { public function supports(AttributeContext $context): bool { return true; } public function evaluate(AttributeContext $context): bool { $currentHour = (int) date('H'); // Allow access only between 08:00 AM and 06:00 PM return $currentHour >= 8 && $currentHour < 18; } }
Rule 3: Evaluate Dynamic Runtime Attributes
namespace App\Abac\Rules; use ExpertApps\LaravelAbac\Contracts\AbacRuleInterface; use ExpertApps\LaravelAbac\Domain\AttributeContext; final readonly class ExportFeeLimitRule implements AbacRuleInterface { public function supports(AttributeContext $context): bool { return $context->hasAttribute('requested_export_limit'); } public function evaluate(AttributeContext $context): bool { $requestedLimit = $context->getAttribute('requested_export_limit'); // Maximum allowed export threshold is 5000 return $requestedLimit <= 5000; } }
Step 2: Create a Policy Group (Action Matrix)
Create a Policy Group to bundle target resources and map actions to rules:
php artisan make:abac-policy-group PatientRecordPolicyGroup
Implement your action matrix:
namespace App\Abac\Policies; use ExpertApps\LaravelAbac\Domain\AbstractPolicyGroup; use ExpertApps\LaravelAbac\Domain\AttributeContext; use App\Models\PatientRecord; use App\Models\User; use App\Abac\Rules\DepartmentMatchRule; use App\Abac\Rules\WorkingHoursRule; use App\Abac\Rules\ExportFeeLimitRule; final class PatientRecordPolicyGroup extends AbstractPolicyGroup { public function targetResource(): string { return PatientRecord::class; } /** * Fast-Pass Hook: Super Admins bypass granular rules immediately. */ public function before(AttributeContext $context): ?bool { if ($context->subject instanceof User && $context->subject->is_super_admin) { return true; } return null; // Continue standard rule evaluation } /** * Action Matrix: Mapping Actions to Required Rules. * All listed rules under an action MUST evaluate to true. */ public function actionRules(): array { return [ 'view' => [ DepartmentMatchRule::class, WorkingHoursRule::class, ], 'export' => [ DepartmentMatchRule::class, WorkingHoursRule::class, ExportFeeLimitRule::class, ], ]; } }
Step 3: Register Policy Group in Configuration
Add your Policy Group class to config/abac.php:
return [ 'policy_groups' => [ App\Abac\Policies\PatientRecordPolicyGroup::class, ], 'fail_closed' => true, ];
💻 Authorization Usage Methods
1. Programmatic Authorization via Facade
use ExpertApps\LaravelAbac\Facades\Abac; use ExpertApps\LaravelAbac\Domain\AttributeContext; $context = AttributeContext::make( subject: auth()->user(), resource: $patientRecord, action: 'export', attributes: [ 'requested_export_limit' => 2500, // Dynamic runtime parameters ] ); if (Abac::isAllowed($context)) { // Perform export action }
2. Native Laravel Controllers ($this->authorize)
laravel-abac integrates seamlessly with standard Laravel authorization methods. You can pass dynamic runtime attributes as an array parameter:
namespace App\Http\Controllers; use App\Models\PatientRecord; use Illuminate\Http\Request; class PatientRecordController extends Controller { public function export(Request $request, PatientRecord $record) { // Pass dynamic attributes via standard authorize method $this->authorize('export', [$record, ['requested_export_limit' => $request->input('limit', 1000)] ]); return response()->json(['message' => 'Export successful']); } }
3. Native Gate Facade (Gate::allows)
use Illuminate\Support\Facades\Gate; if (Gate::allows('view', [$patientRecord])) { // User is authorized to view }
4. Blade Directives
{{-- Simple authorization check --}} @abacAllowed('view', $patientRecord) <button class="btn btn-primary">View Medical Record</button> @else <div class="alert alert-danger">Access Restricted</div> @endabacAllowed {{-- Authorization check with dynamic runtime parameters --}} @abacAllowed('export', $patientRecord, ['requested_export_limit' => 3000]) <a href="{{ route('records.export', $patientRecord) }}">Export Document</a> @endabacAllowed
🏛️ Architecture Flow
+------------------+
| AttributeContext | (Subject, Resource, Action, Dynamic Attributes)
+------------------+
|
v
+------------------+
| AbacEngine |
+------------------+
|
v
+------------------+
| AbacRuleRegistry | (O(1) Map Lookup by Resource & Action)
+------------------+
|
v
+-----------------------+
| AbstractPolicyGroup | ---> before() [Fast-Pass Check]
+-----------------------+
|
v
+------------------+
| Action Matrix | ---> Evaluates sequentially mapped AbacRuleInterface instances
+------------------+
|
v
+------------------+
| Decision | ---> Allow (true) / Deny (false)
+------------------+
🔒 Security Model
- Zero-Trust / Fail-Closed Default: If a resource or action is unmapped, access is denied (
false). - Short-Circuit Exemption: Trusted entities (e.g., Super-Admins) can bypass granular checks safely via
before(). - Dynamic Context Validation: Real-time variables (IP addresses, monetary values, time slots) are validated instantly in memory per request.
🧪 Testing
Run test suites using PHPUnit or Pest:
# Using PHPUnit ./vendor/bin/phpunit # Using Pest PHP ./vendor/bin/pest
🛡️ Security & Vulnerabilities
If you discover any security vulnerabilities within laravel-abac, please email mohamed.abdelazim@expertapps.com.sa.
📜 License
The MIT License (MIT). Please see LICENSE for more information. Developed with ❤️ by ExpertApps.