Concept
Laravel Access Control helps you secure your application in the same place. Secure queries and endpoints using the same logic !
Here is a simple diagram of the concept:
First, we need to define perimeters:
Perimeters
Perimeters permit defining the possible authorization scopes of your applications in a hierarchical way.
Let's take an example together, your projects define multiple scopes of authorizations for your task list application. An administrator can access all tasks, a developer can only manage his client's tickets and all other users can only access their own tasks.
And there you have it, you have your perimeters:
Controls
Now that we have our perimeters, we need to define them for each model in our application by defining a Control
class for each.
Defined controls concern perimeters and configure them for the specific model. Let’s take a look at our TaskControl
class:
class TaskControl extends Control
{
protected function perimeters(): array
{
return [];
}
}
Our Control class is pretty simple, but as you can see, we now need to define our perimeters. Let’s look at the Global Perimeter definition:
class TaskControl extends Control
{
protected function perimeters(): array
{
return [
GlobalPerimeter::new()
->allowed(function (Model $user, string $method) {
return $user->can(sprintf('%s global models', $method));
})
->should(function (Model $user, Model $model) {
return true;
})
->query(function (Builder $query, Model $user) {
return $query;
}),
];
}
}
Let's understand step by step, the allowed
method configure the perimeter access for the current model, here we are checking if
the user can pass the defined gate depending on the method
invoked. This will give view global models
for example when invoking the view
method of the policy.
If a perimeter is allowed, it will then apply using should
in a Policy context and query
in an Eloquent Builder context. Also, it won't
trigger any other perimeter if allowed, you can change that by using an OverlayPerimeter
Now we'll see the ClientPerimeter:
class TaskControl extends Control
{
protected function perimeters(): array
{
return [
GlobalPerimeter::new()
->allowed(function (Model $user, string $method) {
return $user->can(sprintf('%s global models', $method));
})
->should(function (Model $user, Model $model) {
return true;
})
->query(function (Builder $query, Model $user) {
return $query;
}),
ClientPerimeter::new()
->allowed(function (Model $user, string $method) {
return $user->can(sprintf('%s client models', $method));
})
->should(function (Model $user, Model $model) {
return $model->client()->is($user->client);
})
->query(function (Builder $query, Model $user) {
return $query->where('client_id', $user->client->getKey());
}),
];
}
}
We use the same logic for the allowed
method by using a gate on the specified user.
The should
method now verifies if the specified task is linked by the same client as the user.
The query
method now applies a limitation on the query by specifying the client_id requested for the tasks.
And you have it! If the global perimeter doesn’t apply, it then switches to the client perimeter, and so on.
Policies
Now define your task policy to connect to access control:
use Lomkit\Access\Policies\ControlledPolicy;
use App\Models\Task;
class TaskPolicy extends ControlledPolicy
{
protected string $model = Task::class;
}
Your policy is ready to be used
$user->can('view', App\Models\Task::first())
Queries
Apply your control to your query:
App\Models\Task::controlled()->get()