Security Tip: Don't Forget About Policy Filters!
[Tip#2] Policy Filters let you implement shared authorisation checks across your entire policy without repeating code in every method.
Laravel includes an authorization feature known as Policies, which allow you to wrap up authorization rules relating to a specific model within a class. You can find all the details in the Laravel documentation, but in a nutshell, you have these main methods within the policy, which reflect the different abilities a user can perform on a model:
viewAny(User $user)
view(User $user, Tip $tip)
create(User $user)
update(User $user, Tip $tip)
delete(User $user, Tip $tip)
restore(User $user, Tip $tip)
forceDelete(User $user, Tip $tip)
As a reference, here is a blank policy object: https://gist.github.com/valorin/f51de37759eeac84eb75433d7bbcd25c.
Policy objects on their own are great, and give you a lot of flexibility when setting up authorisation rules within your apps. However, consider the scenario where you have admin users who are allowed every ability. You will find yourself repeating this multi-conditional pattern in every policy method:
public function view(User $user, Tip $tip)
{
return $user->admin || $tip->user_id === $user->id;
}
While this does work, it’s ugly and relies on you including the admin conditional in every single method. If that behaviour changes (maybe you switch from an admin flag to user level), you’re stuck updating every single instance of the conditional in a class… which isn’t fun. Or maybe you need to add another check - a moderator has admin powers for this specific model. Now you’ve got three conditionals replicated in every ability method… 😔
The solution is to use a Policy Filter!
A Policy Filter is a before()
method that runs before the ability specific methods run. It allows you to define your policy-wide rules once, removing the code duplication.
public function before(User $user, $ability)
{
if ($user->isAdmin() || $user->isTipModerator()) {
return true;
}
}
The before()
method has three return states:
true
- Grant access to the user, skipping the ability specific method.false
- Block access to the user, skipping the ability specific method.null/void
- Run the ability specific method to check access.
With these states, you can easily use this feature to do things like allowing admin users or blocking suspended users, without making a mess of your ability-specific logic. You could also wrap the before()
method into a trait to reuse between policies.
To see it in action: https://gist.github.com/valorin/c7830b9e300db3ef977c11657659ac7b
Found this security tip helpful? Don't forget to subscribe to receive new Security Tips each week, and upgrade to a premium subscription to receive monthly In Depth articles, or toss a coin in the tip jar.
You can follow Stephen on Twitter, Fediverse, LinkedIn, Pinkary, Bluesky, and Threads, and reach out if you're looking for a Laravel Security Audit and Penetration Test or a budget-friendly Security Review.
Finally, to learn more about security through a series of interactive challenges, check out Stephen's new course: Practical Laravel Security.