Using Laravel's Gate / Authorization in VueJs
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.
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.