Implementing Laravel’s Authorization on the Front-End
If you are using Laravel, probably you are familiar with its ACL. You can easily restrict user’s access by creating policies and binding them to the specific models. It works perfectly on the back-end, but what can we do if our app is something like a SPA?
The Package
We made a package for Laravel that helps to implement Laravel’s authorization easily on front-end. You may give it a shot: https://github.com/thepinecode/policy.
A Short Overview of Laravel’s ACL
The basic workflow is straightforward. We have some users, but we don’t want to provide the same permissions for all of them. There is some hierarchy, and somehow we want our app to represent it.
In real life that means, we let some users create/delete a resource, while others can only read those. Of course, for more complex applications, these rules can be very detailed.
We can define our rules in policies, and we can bind policies to models in the AuthServiceProvider. We can generate these Policy classes with an artisan command, like make:policy PostPolicy.
To dig it deeper, you should read the docs. It explains how can you write different rules for different actions, how can you use them as blade directives, middleware or how can you call a policy check from the user’s side. Also, it provides some introduction about the Gate facade.
Push the Authorization to Front-End
Since every change is committed at the server side, we easily can protect ourselves, because we can use Laravel’s ACL system. We send a request, and it contains our API token, so Laravel can identify us and determine if we are allowed to do the action or not.
So since we protected ourselves on the server side, we don’t have to represent the same on the front-end. But still, what if we want to show/hide UI elements, allow/disable routes for users based on their permissions.
If we are working on a SPA with Vue, we can’t use Laravel’s @can – @cannot blade directives. Somehow, we need to create a lightweight copy of Laravel’s ACL system.
So what we need for that:
- The authenticated user on the front-end
- A cheap copy of the Gate facade
- Policies where we can determine if the user can trigger the action or not
So far we know what we want, let’s prepare it!
Some Preparation
First, we need to push our currently authenticated user to a JS object:
// Somewhere at the closing body tag @auth <script> window.user = @json(auth()->user()) </script> @endauth
From now, we can access our user’s data from the window object.
The Gate class and the Policies
Due to the Laravel way, we may want the following steps:
- We want a place, where we can check if the user can trigger the proper action
- We want to separate this test for different types of models
- We want to check also if the user can do any action and no more truth tests needed
Create the Gate class and the policies and make it accessible from the Vue instance quickly.
// PostPolicy.js export default class PostPolicy { static create(user) { return user.role === 'editor'; } static view(user, post) { return true; } static delete(user, post) { return user.id === post.user_id; } static update(user, post) { return user.id === post.user_id; } }
As you can see, we created the basic CRUD methods and testing the user against some truth tests. Of course, these determination criteria could be anything; we just want to represent the underlying structure.
Now, let’s create the Gate class, where we import the policies and declare the methods what we can call from our Vue instance.
// Gate.js import PostPolicy from './PostPolicy'; export default class Gate { constructor(user) { this.user = user; this.policies = { post: PostPolicy }; } before() { return this.user.role === 'owner'; } allow(action, type, model = null) { if (this.before()) { return true; } return this.policies[type][action](this.user, model); } deny(action, type, model = null) { return ! this.allow(action, type, model); } }
In the constructor, we can add as many policies we want. We have the same interface for all of them through the Gate class. It’s a good way to separate the small and event detailed logic from each other, but store and use them in the same way. Following the convention, the policy for products would be ProductPolicy, and we would pair it with the product key in the policies object.
If the before method returns true, we do not check for any other condition. It overrides everything.
In the allow method, the second parameter refers to the policy, the first onto the method in the policy. The third parameter is optional, it’s the model itself, but since we don’t need it in every check, it’s not required.
The deny method is just the inverse of the allow method.
Use the Gate class with Vue
We can integrate our little “service” to Vue easily in the bootstrap.js file, what Laravel automatically provides with a fresh install.
// bootstrap.js import Gate from './Gate'; Vue.prototype.$gate = new Gate(window.user);
Now we can use the permission checks efficiently from anywhere. Let’s say we had an API call where we got a list of posts. We want to add an edit button only to those where we can edit the post.
<ul> <li v-for="post in posts">{{ post.title }} <a :href="post.edit_route" v-if="$gate.allow('update', 'post', post)">Edit<a></li> </ul>
Summary
This approach can be a helpful and fluent way to check if the user is allowed to do an action or not. Because we tried to follow the way how Laravel’s ACL works, it looks familiar and easy to use. Also, the neat integration with Vue can be a good point, especially if we are working with a SPA and a lots components.
Here you can find an integrated example with Vue: https://jsfiddle.net/8pchn5b7/7/
Need a web developer? Maybe we can help, get in touch!