In this lesson, permissions will be managed on the front and back end. We will again pass the permissions to Vue using the shared data Middleware.
So, in the HandleInertiaRequests
Middleware, you pass the permissions. Those permissions can come from some package or your own custom implementation. For example, let's add two permissions.
app/Http/Middleware/HandleInertiaRequests.php:
class HandleInertiaRequests extends Middleware{ // ... public function share(Request $request): array { return array_merge(parent::share($request), [ 'flash' => [ 'message' => fn () => $request->session()->get('message') ], 'user' => [ 'name' => $request->user()?->name, 'email' => $request->user()?->email, ], 'permissions' => [ 'posts_view' => true, 'posts_manage' => true, ], ]); }}
Next, we can add permissions as props so that we would need to do $page.props
every time.
resources/js/Pages/Posts/Index.vue:
<script setup>import AppLayout from '../../Layouts/App.vue';import { Head, Link, router } from '@inertiajs/vue3'; const props = defineProps({ posts: { type: Object, required: true }, permissions: { type: Object, } }) const destroy = (id) => { if (confirm('Are you sure?')) { router.delete(route('posts.destroy', id)) }}</script> // ...
Now, we can add a check for the Edit button.
resources/js/Pages/Posts/Index.vue:
// ... <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> <Link :href="route('posts.edit', post.id)" class="mr-2 inline-block rounded-md bg-blue-500 px-3 py-2 text-xs font-semibold uppercase tracking-widest text-white shadow-sm"> <Link v-if="permissions.posts_manage" :href="route('posts.edit', post.id)" class="mr-2 inline-block rounded-md bg-blue-500 px-3 py-2 text-xs font-semibold uppercase tracking-widest text-white shadow-sm"> Edit </Link> <button @click="destroy(post.id)" type="button" class="rounded-md bg-red-600 px-3 py-2 text-xs font-semibold uppercase tracking-widest text-white shadow-sm"> Delete </button></td> // ...
On the posts page, everything stayed the same.
But, if we set posts_manage
to false
, the edit button will be gone.
app/Http/Middleware/HandleInertiaRequests.php:
class HandleInertiaRequests extends Middleware{ // ... public function share(Request $request): array { return array_merge(parent::share($request), [ 'flash' => [ 'message' => fn () => $request->session()->get('message') ], 'user' => [ 'name' => $request->user()?->name, 'email' => $request->user()?->email, ], 'permissions' => [ 'posts_view' => true, 'posts_manage' => false, ], ]); }}
This is the principle. You pass the permissions from HandleInertiaRequests
, and add the if-statements where you need to show/hide based on the permissions.
But this is only the front-end part. We also must protect the backend because what if someone guesses the URL?
Let's add a simple hard-coded rule to the permissions: like, users with the ID of 1 and 2 can view the post but only users with the ID of 1 can manage the posts.
app/Http/Middleware/HandleInertiaRequests.php:
class HandleInertiaRequests extends Middleware{ // ... public function share(Request $request): array { return array_merge(parent::share($request), [ 'flash' => [ 'message' => fn () => $request->session()->get('message') ], 'user' => [ 'name' => $request->user()?->name, 'email' => $request->user()?->email, ], 'permissions' => [ 'posts_view' => true, 'posts_manage' => false, 'posts_view' => in_array(auth()->id(), [1, 2]), 'posts_manage' => auth()->id() === 1, ], ]); }}
And let's add checks for other buttons.
resources/js/Pages/Posts/Index.vue:
// ... <template> <Head title="Posts" /> <AppLayout> <Link :href="route('posts.create')" class="mb-4 inline-block rounded-md bg-blue-500 px-4 py-3 text-xs font-semibold uppercase tracking-widest text-white shadow-sm"> <Link v-if="permissions.posts_manage" :href="route('posts.create')" class="mb-4 inline-block rounded-md bg-blue-500 px-4 py-3 text-xs font-semibold uppercase tracking-widest text-white shadow-sm"> Add new post </Link> <table class="min-w-full border divide-y divide-gray-200"> <thead> <tr> <th class="bg-gray-50 px-6 py-3 text-left"> <span class="text-xs font-medium uppercase leading-4 tracking-wider text-gray-500">ID</span> </th> <th class="bg-gray-50 px-6 py-3 text-left"> <span class="text-xs font-medium uppercase leading-4 tracking-wider text-gray-500">Title</span> </th> <th class="bg-gray-50 px-6 py-3 text-left"> <span class="text-xs font-medium uppercase leading-4 tracking-wider text-gray-500">Content</span> </th> <th class="bg-gray-50 px-6 py-3 text-left"> <span class="text-xs font-medium uppercase leading-4 tracking-wider text-gray-500">Created At</span> </th> <th class="bg-gray-50 px-6 py-3 text-left"></th> </tr> </thead> <tbody class="bg-white divide-y divide-gray-200 divide-solid"> <tr v-for="post in posts.data"> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> {{ post.id }} </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> {{ post.title }} </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> {{ post.content }} </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> {{ post.created_at }} </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> <Link v-if="permissions.posts_manage" :href="route('posts.edit', post.id)" class="mr-2 inline-block rounded-md bg-blue-500 px-3 py-2 text-xs font-semibold uppercase tracking-widest text-white shadow-sm"> Edit </Link> <button @click="destroy(post.id)" type="button" class="rounded-md bg-red-600 px-3 py-2 text-xs font-semibold uppercase tracking-widest text-white shadow-sm"> <button v-if="permissions.posts_manage" @click="destroy(post.id)" type="button" class="rounded-md bg-red-600 px-3 py-2 text-xs font-semibold uppercase tracking-widest text-white shadow-sm"> Delete </button> </td> </tr> </tbody> </table> </AppLayout></template>
For the back-end, we need to implement, for example, Gates. But so that we don't have code duplication, let's move those permissions to the User Model as an Attribute.
app/Models/User.php:
class User extends Authenticatable{ // ... protected function permissions(): Attribute { return Attribute::make( get: function () { return [ 'posts_view' => in_array($this->id, [1, 2]), 'posts_manage' => $this->id == 1, ]; } ); }}
app/Http/Middleware/HandleInertiaRequests.php:
class HandleInertiaRequests extends Middleware{ // ... public function share(Request $request): array { return array_merge(parent::share($request), [ 'flash' => [ 'message' => fn () => $request->session()->get('message') ], 'user' => [ 'name' => $request->user()?->name, 'email' => $request->user()?->email, ], 'permissions' => [ 'posts_view' => in_array(auth()->id(), [1, 2]), 'posts_manage' => auth()->id() === 1, ], 'permissions' => auth()->user()?->permissions, ]); }}
In the Controller, we can use Policies.
php artisan make:policy PostPolicy --model=Post
In this example, we will use only the viewAny()
and create()
methods from the Policy. The Policy is registered automatically, so we don't need to register it manually.
app/Policies/PostPolicy.php:
class PostPolicy{ public function viewAny(User $user): bool { return $user->permissions['posts_view']; } public function create(User $user): bool { return $user->permissions['posts_manage']; }}
Now, we can use this Policy in the Controller.
app/Http/Controllers/PostController.php:
use Illuminate\Support\Facades\Gate; class PostController extends Controller{ public function index(): Response { Gate::authorize('viewAny', Post::class); $posts = PostResource::collection(Post::all()); return Inertia::render('Posts/Index', compact('posts')); } public function create(): Response { Gate::authorize('create', Post::class); return Inertia::render('Posts/Create'); } public function store(StorePostRequest $request): RedirectResponse { Gate::authorize('create', Post::class); Post::create($request->validated()); return redirect()->route('posts.index') ->with('message', 'Post created successfully.'); } public function edit(Post $post) { Gate::authorize('create', Post::class); return Inertia::render('Posts/Edit', compact('post')); } public function update(Post $post, StorePostRequest $request) { Gate::authorize('create', Post::class); $post->update($request->validated()); return redirect()->route('posts.index') ->with('message', 'Post updated successfully'); } public function destroy(Post $post) { Gate::authorize('create', Post::class); $post->delete(); return redirect()->route('posts.index') ->with('message', 'Post deleted successfully'); }}
If the user even guesses the URL now, they won't be able to access those protected pages.
This is one way how to add permissions to your Inertia application.
We have a tutorial Laravel Inertia Roles & Permissions: Breeze/Jetstream Examples where roles and permissions are used in the Inertia application.
You can find the source code for this course on GitHub.