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 an SPA?

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:

  1. The authenticated user on the front-end
  2. A cheap copy of the Gate facade
  3. 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
Note, we are using @auth – @endauth blade directives and the @json directives. Both are quite new, so please make sure you have a recent Laravel version. We can use these since we need this data only once at the very first load before the router activated.

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:

  1. We want a place, where we can check if the user can trigger the proper action
  2. We want to separate this test for different types of models
  3. 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;
    }
}
Note that, we are using static methods here. This way we can call the methods without initializing a new instance of the Policy.

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);
Note, instead of signing it to the window object, we bind it to Vue directly. That makes us able to call the gate class like this.$gate from the Vue instance, or simply $gate from a Vue component’s template part.

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/