thepinecode/policy

Using Laravel's authorization on the front-end.


License
MIT

Documentation

Policy

Using Laravel's authorization on the front-end.

A nice tool for SPAs and front-end heavy applications.

If you want to see behind the package, we suggest to read this post: Implementing Laravel’s Authorization on the Front-End.

Table of contents

  1. Getting Started
  2. Publishing and setting up the JavaScript library
  3. Using the policies and the Gate.js
  4. Example
  5. Contribute

Getting started

You can install the package with composer, running the composer require thepinecode/policy command.

Since the package supports auto-discovery, Laravel will register the service provider automatically behind the scenes.

In some cases you may disable auto-discovery for this package. You can add the provider class to the dont-discover array to disable it. Then you need to register it manually again.

Publishing and setting up the JavaScript library

By default the package provides a Gate.js file, that will handle the policies. Use the php artisan vendor:publish command and choose the Pine\Policy\PolicyServiceProvider provider. After publishing you can find your fresh copy in the resources/js/policies folder if you are using Laravel 5.7+. If your application is lower than 5.7, the JS will be published in the resources/assets/js/policies.

Setting up the Gate.js

Then you can import the Gate class and assign it to the window object.

import Gate from './policies/Gate';
window.Gate = Gate;

Initializing a gate instance

From this point you can initialize the translation service anywhere from your application.

let gate = new Gate;

Passing the user to the gate instance

The Gate object requires a passed user to work properly. This can be a string or an object. By default, it looks for the window['user'] object, however you may customize the key or the object itself.

let gate = new Gate; // window['user']

let gate = new Gate('admin'); // window['admin']

let gate = new Gate({ ... }); // uses the custom object

Note, you can pass any object as a user. If you pass a team or a group object, it works as well. Since you define the logic behind the Gate, you can pass anything you wish.

Using it as a Vue service

If you want to use it from Vue templates directly you can extend Vue with this easily.

Vue.prototype.$Gate = new Gate;
<template>
    <div v-if="$Gate.allow('view', model)">...</div>
</template>
computed: {
    hasPermission: {
        return this.$Gate.allow('view', this.model);
    }
}

The @currentUser blade directive

To make it quicker, the package comes with a @currentUser blade directive. This does nothing more, but to print the currently authenticated user as JSON and assign it to the window object.

@currentUser

<!-- Result -->
<script>window['user'] = { ... };</script>

You may override the default key for the user. You can do that by passing a string to the blade directive.

@currentUser ('admin')

<!-- Result -->
<script>window['admin'] = { ... };</script>

If there is no authenticated user, the value will be null.

Using the policies and the Gate.js

The available methods

allow()

The allow() accepts two parameters. The first is the action to perform, the second is the model object or the model name, like in Laravel.

Note: model name should be a lower case version of the actual model name in Laravel: for example Comment becomes comment.

gate.allow('view', model);

gate.allow('create', 'comment');

deny()

The deny() has the same signature like allow() but it will negate its return value.

gate.deny('view', model);

gate.deny('create', 'comment');

before()

Like in Laravel, in the before() method you can provide a custom logic to check for special conditions. If the condition passes, the rest of the policy rules in the allow() or deny() won't run at all. However if the condition fails, the policy rules will get place. To use the before() method, you may extend the gate object and define your custom logic.

Gate.prototype.before = function () {
    return this.user.is_admin;
}

Please note, to use the this object correctly, use the traditional function signature instead of the arrow (() => {}) functions.

Adding the UsesModelName trait to the models

Since, the policies use real JSON shaped eloquent models, the models have to use the Pine\Policy\UsesModelName trait that generates the proper model name. This model name attribute is used for pairing the proper policy with the model by the Gate.js.

use Pine\Policy\UsesModelName;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    use UsesModelName;

    protected $appends = ['model_name'];
}

Please note, to be able to use this attribute on the front-end, the attribute has to be appended to the JSON form. You can read more about appending values to JSON in the docs.

Generating policies with artisan

The package comes with an artisan command by default, that helps you to generate your JavaScript policies easily. To make a policy, run the php artisan make:js-policy Model command, where the Model is the model's name.

php artisan make:js-policy Comment

This command will create the CommentPolicy.js file next to the Gate.js in the resources/js/policies directory. If you are using lower than Laravel 5.7, the policies will be generated in the resources/assets/js/policies directory.

Note, the command will append the Policy automatically in the file name. It means you may pass only the model name when running the command.

After you generated the policy files, use npm to compile all the JavaScript, including policies.


Important!

The policies are registered automatically. It means, no need for importing them manually. The gate instance will automatically populate the policies. Every policy will be used where it matches with the model's model_name attribute.

Based on Laravel's default app.js the Gate instance registers the policies automatically when calling npm run dev, npm run prod and so on.


Writing the policy rules

Policies – like in Laravel – have the following methods by default: viewAny, view, create, update, restore, delete and forceDelete. Of course, you can use custom methods as well, policies are fully customizables.

...

view(user, model)
{
    return user.id == model.user_id;
}

create(user)
{
    return user.is_admin;
}

approve(user, model)
{
    return user.is_editor && user.id == model.user_id;
}

...

Example

// app.js
Vue.prototype.$Gate = new Gate;

Vue.component('posts', {
    mounted() {
        axios.get('/api/posts')
            .then(response => this.posts = response.data);
    },
    data() {
        return {
            posts: [],
        };
    },
    template: `
        <ul><li v-for="post in posts" v-if="$Gate.allow('update', post)"></li></ul>
        <button v-if="$Gate.allow('create', 'post')">Create post</button>
    `
});

let app = new Vue({
    //
})
<body>
    <posts></posts>

    @currentUser
    <script src="{{ asset('js/app.js') }}"></script>
</body>

Contribute

If you found a bug or you have an idea connecting the package, feel free to open an issue.