expertapps/laravel-abac

Enterprise-grade Attribute-Based Access Control (ABAC) using Policy Group and Action Matrix Strategy for Laravel.

Maintainers

Package info

github.com/ab0Yazan/laravel-abac

pkg:composer/expertapps/laravel-abac

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.2.2 2026-08-03 14:45 UTC

This package is auto-updated.

Last update: 2026-08-03 17:28:12 UTC


README

Latest Version on Packagist Downloads PHP Laravel License

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.