The Permit package provides an Authorization system , for Anchor applications featuring hierarchical RBAC (Role-Based Access Control) and granular ABAC (Attribute-Based Access Control).
Permit is designed to be "Secure by Default". If no permission or gate is found, access is automatically denied.
- Hierarchical Roles: Roles can inherit permissions from parent roles (e.g., Admin inherits from Editor).
- Granular Permissions: Define specific abilities and group them for easy management.
- Dynamic Gates: Custom closure-based authorization for complex business logic (ABAC).
- Direct User Overrides: Grant or deny permissions directly to users outside of their roles.
- Super Admin Bypass: Built-in support for a master role that automatically bypasses all permission checks.
- High Performance: Optimized permission caching ensures zero-latency checks in production.
- Smart Middleware: Decouple logic by automatically deriving permissions from URI segments.
Permit is a package that requires installation before use.
php dock package:install Permit --packagesThis will automatically:
- Run database migrations for roles, permissions, and assignments.
- Register the
PermitServiceProvider. - Publish the configuration file.
- Register
CheckPermissionMiddlewareforwebandapigroups.
Configuration file: App/Config/permit.php
return [
'super_admin_role' => 'super-admin',
'role_hierarchy' => true,
'cache' => [
'enabled' => true,
'ttl' => 3600,
],
'user_model' => App\Models\User::class,
];Add the necessary traits to your User model:
use Permit\Traits\HasRoles;
use Permit\Traits\HasPermissions;
class User extends BaseModel
{
use HasRoles, HasPermissions;
}use Permit\Permit;
// Create role with permissions
Permit::role()
->slug('editor')
->name('Content Editor')
->permissions(['posts.create', 'posts.edit'])
->create();
// Assign to user
$user->assignRole('editor');// Via model trait
if ($user->can('posts.create')) {
// Process creation
}
// Via Facade
if (Permit::can($user, 'users.manage')) {
// ...
}Avoid permission duplication by nesting roles. This creates a chain of command where high-level roles automatically possess the capabilities of lower-level ones.
Note
Inheritance is Additive: By default, a child role receives all permissions from its parent. There is no "subtractive inheritance" at the role level.
Real-World Scenario: Media CMS
Guest: Canview.postsEditor: InheritsGuest+create.posts,edit.postsManager: InheritsEditor+publish.posts,delete.postsAdmin: InheritsManager+manage.users,manage.settings
Permit::role()
->slug('admin')
->inherits('editor')
->permissions(['settings.manage'])
->create();If you need a role that is "like an Editor but without delete powers", you have two options:
- Direct User Override (Recommended): If it's for a specific person, use
$user->denyPermissionTo('posts.delete'). - Flat Role Definition: Create a new role that doesn't inherit, and manually assign the subset of permissions.
- Mid-level Inheritance: Create a
BasicEditorrole with common powers, then haveFullEditor(with delete) andRestrictedEditor(without delete) inherit fromBasicEditor.
Define complex logic that goes beyond simple permissions (ABAC). Gates are ideal for implementing "Ownership" logic or time-based restrictions.
Custom gates should be defined in the
boot()method of a Service Provider, for exampleApp\Providers\AuthServiceProvider.php.
Real-World Scenario: Resource Ownership Only the creator of a post (or an admin) should be able to edit it.
Permit::define('update-post', function ($user, $post) {
return $user->id === $post->author_id || $user->hasRole('admin');
});
// Using the gate in a Controller
if (Permit::can($user, 'update-post', $post)) {
// Current user is the author or an admin
}While roles handle bulk permissions, sometimes you need to override a role for a specific individual. Permit allows you to Grant or Deny permissions directly to a user.
Direct Deny takes absolute precedence. Even if a user's role grants them a permission, if it is explicitly denied on the user level,
can()will returnFALSE.
Real-World Scenario: Suspended Privileges An "Editor" is currently under review and should not be allowed to delete posts, even though the 'editor' role usually allows it.
$user = User::find(123);
// Explicitly take away the ability
$user->denyPermissionTo('posts.delete');
// This now returns FALSE, ignoring role inheritance
if ($user->can('posts.delete')) { ... }Monitor access patterns and security health. This is vital for auditing which permissions are being used and identifying potential security risks.
$analytics = Permit::analytics();
// Real-world: Get distribution of roles across the user base
$distribution = $analytics->getRoleDistribution();
// Real-world: Identify most common authorization failures (potential attacks)
$failures = $analytics->getFailureLogs(10);
// Real-world: Get permissions grouped by module
$groups = $analytics->getPermissionsByGroup();Here is how you would implement a full authorization cycle for a Blog application.
App/storage/database/seeds/RolesSeeder.php
This file doesn't exist by default. It is a recommended location to manage your authorization "Source of Truth". You can create it manually to define your roles and permissions in code.
Use the builder to create grouped permissions for clarity.
use Permit\Permit;
// Define permissions
Permit::permission()->slug('posts.create')->group('Writing')->create();
Permit::permission()->slug('posts.edit')->group('Writing')->create();
Permit::permission()->slug('posts.delete')->group('Admin')->create();Configure the hierarchical structure.
// Editor can write and edit
Permit::role()
->slug('editor')
->permissions(['posts.create', 'posts.edit'])
->create();
// Admin inherits Editor powers and adds deletion
Permit::role()
->slug('admin')
->inherits('editor')
->permissions(['posts.delete'])
->create();Handle resource-specific ownership.
Permit::define('manage-account', function ($user, $targetUser) {
return $user->id === $targetUser->id;
});// Assigning during registration
$user->assignRole('editor');
// Checking in a Middleware or Controller
if ($user->can('posts.delete')) {
// This will return FALSE for Editor, TRUE for Admin
}| Method | Description |
|---|---|
role() |
Starts a fluent RoleBuilder. |
permission() |
Starts a fluent PermissionBuilder. |
can($user, $ability) |
Evaluates if a user has authorization. |
cannot($user, $ability) |
Inverse of can(). |
hasAnyRole($user, $arr) |
Checks if user has any of the listed roles. |
hasAllRoles($user, $arr) |
Checks if user has all of the listed roles. |
define($name, $closure) |
Registers a custom authorization gate. |
analytics() |
Returns the PermitAnalytics service. |
clearCache() |
Wipes the permission cache. |
Access via Permit::permission().
| Method | Description |
|---|---|
slug($slug) |
Sets the unique identifier for the permission. |
name($name) |
Sets a human-readable name. |
group($group) |
Organizes permissions into UI categories. |
create() |
Persists the permission to the database. |
| Method | Description |
|---|---|
assignRole($slug) |
Grants a role to the user. |
removeRole($slug) |
Removes a role from the user. |
hasRole($slug) |
Checks if the user has a specific role. |
can($ability) |
Standard Laravel-style permission check. |
givePermissionTo($slug) |
Grants a direct (un-rolled) permission. |
denyPermissionTo($slug) |
Explicitly denies a permission (override). |
revokePermissionTo($slug) |
Removes a direct grant or deny. |
getAllPermissions() |
Returns an array of all Permission models. |
getPermissionNames() |
Returns an array of permission slugs. |
Access via Permit::gates(). This service manages closure-based authorization (Gates) and model-based Policies.
| Method | Description |
|---|---|
define($name, $closure) |
Register a custom gate. |
check($ability, $user, $params) |
Manually evaluate a gate. |
resource($name, $policyClass) |
Automatically map CRUD abilities (view, update, etc.) to a Policy class. |
before($closure) |
Register a callback that runs before all authorization checks. |
after($closure) |
Register a callback that runs after all authorization checks. |
| Command | Description |
|---|---|
php dock permit:sync |
Syncs permissions from config/code to the database. |
php dock permit:cache |
Clears the authorization cache for immediate updates. |
| Attribute | Type | Description |
|---|---|---|
slug |
string |
Unique identifier (e.g., 'admin'). |
permissions |
relation |
BelongsToMany relationship to permissions. |
parent_id |
integer |
ID of the parent role for inheritance. |
Permit includes a Smart Middleware that automatically enforces permissions based on URI conventions.
The middleware scans URI segments from right to left to find a recognized action (mapped in config). Once found, it assumes the preceding segment is the resource.
Example:
account/user/create→ Action:create, Resource:user→ Permission:users.create(automatically pluralized).
Customize behavior in App/Config/permit.php:
'smart_middleware' => [
'enabled' => true,
'action_map' => [
'create' => 'create',
'store' => 'create',
'edit' => 'edit',
'update' => 'edit',
'destroy' => 'delete',
'index' => 'manage',
],
],When "Smart Middleware" is enabled, it automatically hydrates the Request object with route context. This data can be retrieved anywhere in your application (Controllers, Views, etc.).
// Get the structured permission (e.g., 'users.create')
$permission = $request->getRoutePermission();
// Get specific resource/action
$resource = $request->getRouteContext('resource'); // 'users'
$action = $request->getRouteContext('action'); // 'create'The metadata is also exposed directly in your view templates via private methods in the ViewEngine.
$this->canAccessAction(string|array $action): Checks if the user has permission for the specified action(s) on the current resource.$this->isResourceActive(string $name): Returns 'active' if the current resource matches the name.$this->getRouteTitle(): Retrieves a human-readable title frompermit.titlesconfig.$this->getBreadcrumbs(): Generates a standard breadcrumb array based on context.
Example:
<li class="<?= $this->isResourceActive('users') ?>">Users</li>
<?php if ($this->canAccessAction(['edit', 'delete'])): ?>
<button>Manage Record</button>
<?php endif; ?>This metadata enabling advanced features such as context-aware logging, automatic sidebar highlighting, and dynamic breadcrumbs without manual URL parsing.
The Permit package includes an automated cache warming task that is registered in the framework scheduler. This ensures permission data is efficiently cached for performance.
// packages/Permit/Schedules/PermitCacheSchedule.php
namespace Permit\Schedules;
use Cron\Interfaces\Schedulable;
use Cron\Schedule;
class PermitCacheSchedule implements Schedulable
{
public function schedule(Schedule $schedule): void
{
$schedule->task()
->signature('permit:cache')
->daily();
}
}| Error/Log | Cause | Solution |
|---|---|---|
| Permission not reflecting | Cache hasn't expired yet. | Run php dock permit:cache to clear. |
| "Unauthorized" exception | User lacks role or direct permission. | Check assignments via $user->roles(). |
| Cyclic inheritance error | Role is trying to inherit from itself. | Verify the inherits() slug in builder. |
- Principle of Least Privilege: Start with zero permissions and grant only what is necessary.
- Prefer Roles: Use roles for bulk management and direct permissions only for exceptional overrides.
- Explicit Gates: Use Gates for resource-specific logic (e.g., "owner of object") instead of generic "edit" permissions.
- Audit Failures: Monitor
analytics()->getFailureLogs()to detect unauthorized access attempts.