Marwa Framework provides a production-ready authorization system based on Policy Classes, Permission Strings, and RBAC (Role-Based Access Control).
Gate - Authorization servicePolicyRegistry - Policy resolverAuthorizationException - 403-style exceptionauthorize() helper functionYour User model must implement UserInterface or have these methods:
// app/Models/User.php
use Marwa\Framework\Authorization\Contracts\UserInterface;
class User extends Model implements UserInterface
{
public function hasPermission(string $permission): bool
{
// Check direct permissions
if (in_array($permission, $this->permissions ?? [], true)) {
return true;
}
// Check role-based permissions
foreach ($this->roles ?? [] as $role) {
$rolePermissions = $this->getRolePermissions($role);
if (in_array($permission, $rolePermissions, true)) {
return true;
}
}
return false;
}
public function hasRole(string $role): bool
{
return in_array($role, $this->roles ?? [], true);
}
public function isAdmin(): bool
{
return $this->hasRole('admin');
}
private function getRolePermissions(string $role): array
{
return match ($role) {
'admin' => ['*'],
'editor' => ['blog.post.*', 'blog.category.*'],
'author' => ['blog.post.create', 'blog.post.update', 'blog.post.view'],
default => [],
};
}
}
// app/Providers/AppServiceProvider.php
use Marwa\Framework\Authorization\Gate;
use Marwa\Framework\Authorization\PolicyRegistry;
public function register(): void
{
$gate = app(Gate::class);
$registry = app(PolicyRegistry::class);
// Manual registration
$registry->register(Post::class, PostPolicy::class);
$registry->register(User::class, UserPolicy::class);
}
{module}.{resource}.{action}
Examples:
blog.post.viewAny - List all postsblog.post.view - View a specific postblog.post.create - Create new postblog.post.update - Update a postblog.post.delete - Delete a postblog.post.publish - Custom ability// app/Policies/PostPolicy.php
use Marwa\Framework\Authorization\Policy;
use Marwa\Framework\Authorization\Contracts\UserInterface;
class PostPolicy extends Policy
{
public function viewAny(UserInterface $user): bool
{
return $user->hasPermission('blog.post.viewAny');
}
public function view(UserInterface $user, Post $post): bool
{
// Allow if user has permission OR is owner
return $user->hasPermission('blog.post.view')
|| $this->isOwner($user, $post);
}
public function create(UserInterface $user): bool
{
return $user->hasPermission('blog.post.create');
}
public function update(UserInterface $user, Post $post): bool
{
// Admin or owner can update
return $user->hasPermission('blog.post.update')
|| $this->isOwner($user, $post);
}
public function delete(UserInterface $user, Post $post): bool
{
return $user->hasPermission('blog.post.delete');
}
public function publish(UserInterface $user, Post $post): bool
{
return $user->hasPermission('blog.post.publish');
}
}
// app/Controllers/PostController.php
use Marwa\Framework\Controllers\Controller;
use Marwa\Framework\Controllers\Concerns\AuthorizesRequests;
class PostController extends Controller
{
use AuthorizesRequests;
public function index(): ResponseInterface
{
$this->authorize('viewAny', Post::class);
// or: $this->authorizeClass('blog.post.viewAny');
$posts = Post::all();
return $this->view('posts.index', ['posts' => $posts]);
}
public function show(int $id): ResponseInterface
{
$post = Post::findOrFail($id);
$this->authorize('view', $post);
return $this->view('posts.show', ['post' => $post]);
}
public function store(Request $request): ResponseInterface
{
$this->authorize('create', Post::class);
$post = Post::create($request->all());
return $this->redirect('/posts/' . $post->id);
}
public function update(Request $request, int $id): ResponseInterface
{
$post = Post::findOrFail($id);
// Throws AuthorizationException on failure
$this->authorize('update', $post);
$post->update($request->all());
return $this->json($post);
}
public function destroy(int $id): ResponseInterface
{
$post = Post::findOrFail($id);
$this->authorize('delete', $post);
$post->delete();
return $this->json(['success' => true]);
}
}
// Direct authorization check (returns bool)
can('viewAny', Post::class); // true/false
can('update', $post); // true/false
// Throws exception on failure
authorize('viewAny', Post::class);
authorize('update', $post);
// Gate facade
gate()->allows('viewAny', Post::class);
gate()->denies('delete', $post);
// Using before callback
$gate = app(Gate::class);
$gate->before(function ($user, $ability, $resource) {
if ($user->isAdmin()) {
return true; // Skip policy check for admins
}
return null; // Continue to policy check
});
// routes/web.php
use Marwa\Framework\Middlewares\AuthorizeMiddleware;
// Via middleware parameter
Route::get('/posts', [PostController::class, 'index'])
->middleware('ability:blog.post.viewAny');
Route::get('/posts/{post}/edit', [PostController::class, 'edit'])
->middleware('ability:blog.post.update,post');
Route::delete('/posts/{post}', [PostController::class, 'destroy'])
->middleware('ability:blog.post.delete,post');
// config/app.php
'middlewares' => [
// ...
Marwa\Framework\Middlewares\AuthorizeMiddleware::class,
],
{
"name": "blog",
"version": "1.0.0",
"policies": {
"Post": "Modules\\Blog\\Policies\\PostPolicy",
"Category": "Modules\\Blog\\Policies\\CategoryPolicy"
},
"permissions": [
"blog.post.viewAny",
"blog.post.view",
"blog.post.create",
"blog.post.update",
"blog.post.delete",
"blog.post.publish",
"blog.category.viewAny",
"blog.category.create",
"blog.category.update",
"blog.category.delete"
],
"menu": {
"name": "blog",
"label": "Blog",
"url": "/admin/blog",
"permission": "blog.post.viewAny"
}
}
// Load policies from config
$registry = app(PolicyRegistry::class);
$registry->loadFromConfig(config('auth.policies', []));
// config/auth.php
<?php
return [
'policies' => [
Post::class => App\Policies\PostPolicy::class,
User::class => App\Policies\UserPolicy::class,
],
'permissions' => [
'admin' => ['*'],
'editor' => [
'blog.post.viewAny',
'blog.post.create',
'blog.post.update',
'blog.category.viewAny',
'blog.category.create',
],
'author' => [
'blog.post.viewAny',
'blog.post.create',
'blog.post.update:own',
],
],
];
User
├── id
├── name
├── email
├── roles [] // e.g., ['admin', 'editor']
├── permissions [] // e.g., ['blog.post.create']
└── methods:
├── hasPermission(string $permission): bool
├── hasRole(string $role): bool
└── isAdmin(): bool
Permission Format: {module}.{resource}.{action}
Role → Permissions Mapping: Config-based or DB-driven
$gate = app(Gate::class);
$gate->resource('post', Post::class);
// Equivalent to defining:
// - post.viewAny
// - post.view
// - post.create
// - post.update
// - post.delete
// Custom abilities
$gate->resource('post', Post::class, [
'viewAny', 'view', 'create', 'update', 'delete', 'publish'
]);
use Marwa\Framework\Exceptions\AuthorizationException;
try {
$this->authorize('delete', $post);
} catch (AuthorizationException $e) {
// $e->getAbility() - the action (e.g., 'delete')
// $e->getResource() - the resource (e.g., Post instance)
return $this->forbidden('Cannot delete this post');
}
// Gate facade
Gate::allows('viewAny', Post::class);
Gate::denies('delete', $post);
// Auth facade
Auth::user(); // Get current user
Auth::check(); // Is authenticated?
Auth::id(); // Current user ID