Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Laravel's Gate / Authorization in VueJs

I'm not sure how this hasn't been dealt with before, but how do I go about using VueJs and authorizing actions in Vue template?

If I'm using Laravel's blade, this is easy (using @can directive), but there's no documentation or any way to perform this in Vue after searching for hours on Google.

Now, I know I can simply load the users permissions into an array / JSON object inside the view, but there seems to be no way of displaying / hiding actions in Vue templates using Laravel's gate methods to determine if the user is allowed to perform the action on a specific record.

For example, there's a list of comments, but the user must own the comment to be able to see the 'edit' button.

The thing is, if I implement the logic in Vue, I'd be duplicating authorization logic throughout my entire backend and frontend for this.

Using Laravel's policy's, I'm able to perform complex authorization of specific actions. But I'm stumped as to how I would implement the policy in Vue.

There's also more complex scenarios, such as if a user that has an admin role is browsing comments, they should be able to edit it even if they don't own the comment.

Does anyone have any suggestions for this type of scenario?

EDIT:

Now I could add an attribute accessor to my models, for example:

Model:

class Comment extends Model
{
    protected $appends = ['can_update'];

    public function getCanUpdateAttribute()
    {
        return Gate::allows('update', $this);
    }
}

Vue:

<button v-if="comment.can_update">Edit</button>

But this seems like I'm again duplicating logic that already exists inside my policies.

like image 894
Steve Bauman Avatar asked Aug 24 '17 18:08

Steve Bauman


3 Answers

I ended up using Laravel resources to accomplish this.

Here's an example (notice the can array key):

class Ticket extends Resource
{
    /**
     * The "data" wrapper that should be applied.
     *
     * @var string
     */
    public static $wrap = 'ticket';

    /**
     * Transform the resource into an array.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'answer_id' => $this->answer_id,
            'summary' => $this->summary,
            'description' => $this->description,
            'creator' => $this->creator,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
            'reported_at' => $this->reported_at,
            'closed_at' => $this->closed_at,
            'closed' => $this->closed,
            'answered' => $this->answered,
            'can' => $this->permissions(),
        ];
    }

    /**
     * Returns the permissions of the resource.
     *
     * @return array
     */
    protected function permissions()
    {
        return [
            'update' => Gate::allows('update', $this->resource),
            'delete' => Gate::allows('delete', $this->resource),
            'open' => Gate::allows('open', $this->resource),
        ];
    }
}

This allowed me to control access on the front-end using simple boolean logic in Vue templates, rather than duplicating actual permission logic on the front-end as well:

<router-link v-if="ticket.can.update" :to="{name:'tickets.edit', params: {ticketId: ticket.id}}" class="btn btn-sm btn-secondary">
    <i class="fa fa-edit"></i> Edit
</router-link>

Also, I used Laravel resource collections to be able to apply permissions if the user is able to create a resource:

class TicketCollection extends ResourceCollection
{
    /**
     * The "data" wrapper that should be applied.
     *
     * @var string
     */
    public static $wrap = 'tickets';

    /**
     * Get any additional data that should be returned with the resource array.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return array
     */
    public function with($request)
    {
        return [
            'can' => [
                'create' => Gate::allows('create', Ticket::class),
            ],
        ];
    }
}

Then in my API controller:

public function index()
{
    $tickets = Ticket::paginate(25);

    return new TicketCollection($tickets);
}

public function show(Ticket $ticket)
{
    $ticket->load('media');

    return new TicketResource($ticket);
}

This allowed me to validate if the currently authenticated user has access to be able to create the resource that is being listed, since we won't have an actual resource to validate on, we can do this on the returned collection since it relates to it entirely.

Implementing this pattern seemed to me the simplest way of managing authorization without duplicating the actual authorizing logic throughout my Vue app and using blade to inject permissions into components individually.

Injecting permissions into components eventually lead me to problems if you have nested components that also require permissions, because then you'll need to pass the child components permissions into the parents to be able to validate them.

For nested permissions, you can return sub-resources from your parent resource for relationships that also include a can permissions array, so you can easily loop through these using Vue and use simple logic for determining the users access on those as well.

This approach was also beneficial so I could cache the permissions for each user via server-side on resources that don't change often.

like image 147
Steve Bauman Avatar answered Nov 18 '22 12:11

Steve Bauman


Currently, there is no way to achieve this without duplicating code of the backend into the frontend.

In this episode of Fullstack Radio(17:15), Jeffrey Way and Adam Wathan talked exactly about that point. They have the same opinion as mine and currently, they're doing the same you did.

They also talked about using props like:

<post-component :can-update="{{ $user->can('update', $post) }}"></post-component>

I hope this answer can be helpful.

like image 11
Ian Rodrigues Avatar answered Nov 18 '22 11:11

Ian Rodrigues


In one of my projects, permissions are stored as strings i.e. 'post.read', 'post.view'. These permissions are passed to the front end upon login.

I built a small and simple plugin Vue Quick ACL which abstracts the functionality away and manages the reactivity when changing users and user permissions.

// UserResource

return [
 'name' => $this->name,
 'permissions' => $this->permissions
];

In the frontend you can then store those permissions with the user:

// Login.vue

export default {
  methods: {
    login() {
      Api.login()
        then(({data}) => {
          this.$setUserPermissions(data.permissions)
        })
    }
  }
}
// Component.vue

<template>
  <button v-if="$can('post.delete')">Delete</button>
</template>
like image 1
digout Avatar answered Nov 18 '22 11:11

digout