Laravel: Authorization done right
It’s not about who, but what
The authorization mechanism (not to be mistaken with authentication) included in Laravel is very easy to follow: you check if an authenticated user can or cannot do something in your application, based on its properties and the arguments passed, if any.
To do that, the documentation is very straightforward: you can use Gates for anything, but Policies are the recommended way to deal with Eloquent models since it allows the framework to guess the policy name with very little code. For example, to check if the user can access the dashboard we can use a Gate, but to check if he can publish an article an “Article Policy” makes sense.
In any case, I was reading someone’s code and I saw what I can call a “bad” implementation of authorization. It’s not about what the user properties are, or what attributes allows the user to pass a check, but the authorization itself.
I don’t care if you’re the admin
In our chat application, an user can create a group where others users can join in and broadcast messages to everybody, like your average iMessage or WhatsApp group. In this group, the creator is the “owner”, and can edit or delete it, and this status is intransferible. Other users can be “admins”, that can invite or kick users from the group, and finally normal users who can only chat.
Inside the view, I saw a simple check to determine if the authenticated user could edit the Group.
Our Group model contains the
isOwner() method which will smartly check if an user is the owner of the group without unnecessary database queries. This is the code, if you’re curious.
Going back to the point, doing a simple
IF check is not bad per-se, but doing so is asking for trouble later. What happens when we add a new role called “moderator” with a new set of permissions? What happens if we remove the role? Worse, what happens if we allow guests to create throwaway groups? How we handle that?
When you’re asking if a user can or can not do something, that logic must be inside a policy or gate. Is a common pitfall to not ask for the permission directly. Moving that part of the authorization properly can save us a lot of headaches afterwards.
Doing authorization properly, our
IF block in our view becomes a simple authorization check.
Then, in our Group Policy, we shove the lines we ate before. It’s okay if a policy or gate is made up of a lot of lines as long it’s maintainable, but in this case is (luckily) just one.
This makes the code a lot more maintainable, as we have now centralized the authorization for the user for the group. Obviously this wouldn’t make sense if we’re only checking it once in the whole application, as the inline authorization is great for these cases.
The best thing about centralizing the authorization is that we are a few characters away to from reusing it anywhere. If you’re extending the default controller included in your application, you can use
authorize(), so requests can be properly stopped when the user lacks proper authorization, among other useful alternatives.
For that reason, instead of manually checking if an user or resource complies with a certain requisite, and even throwing an Exception verbatim, it’s just better to write the policy (or gate) with all the checks required.
Nothing is written in stone, so revisiting the authorization gate to modify it will save you a lot of hours instead of modifying each check in all views and controllers.