Permission securing frontend

Step 01: npm installing casl packages casl vue and casl abilities

npm install @casl/vue @casl/ability

Step 02: Defining service abilities inside resources/js/services/ability.js to use it inside app.js

import { AbilityBuilder, Ability } from '@casl/ability' 
const { can, cannot, build } = new AbilityBuilder(Ability);
 
export default build();

Step 03: Now importing ability and abilitiesPlugin from services to app.js

// at the top 
import { abilitiesPlugin } from '@casl/vue'; 
import ability from './services/ability'; 

createApp({
    setup() {
        const { getUser } = useAuth()
        onMounted(getUser)
    }
})
    .use(router)
    .use(VueSweetalert2)
    .use(abilitiesPlugin, ability)  <= services being used in vue 
    .mount('#app')

Step 04: Now, building the /abilities API route inside routes/api.php

Route::group(['middleware' => 'auth:sanctum'], function() {

    ......
    Route::get('abilities', function(Request $request) { 
        return $request->user()->roles()->with('permissions')
            ->get()
            ->pluck('permissions')
            ->flatten()
            ->pluck('name')
            ->unique()
            ->values()
            ->toArray();
    }); 
});

Code above creates unique permissions list in array for a particular logged in user.

Step 05: Using this api call creating getAbilities() method from auth.js composable

// importing casl vue packages and injecting ABILITY_TOKEN to ability variable
// at the top 
......
import { AbilityBuilder, Ability } from '@casl/ability'; 
import { ABILITY_TOKEN } from '@casl/vue'; 


export default function useAuth() {
.....
const ability = inject(ABILITY_TOKEN)

// adding getAbilities() and returning it to component 
const getAbilities = async() => { 
    axios.get('/api/abilities')
        .then(response => {
            const permissions = response.data
            const { can, rules } = new AbilityBuilder(Ability)

            can(permissions)

            ability.update(rules)
        })
}

Code above, after a successful HTTP GET request, assigning all the permissions to a variable from the response. Then adding all the permissions into a can method and updating the abilities.

Step 06: Calling getAbilities() method from loginUser() method using await and async

// changing loginUser() from below 
const loginUser = (response) => {
    user.name = response.data.name 
    user.email = response.data.email 

    localStorage.setItem('loggedIn', JSON.stringify(true))
    router.push({ name: 'issues.list' })
}

// to below 
const loginUser = async (response) => { 
    user.name = response.data.name
    user.email = response.data.email

    localStorage.setItem('loggedIn', JSON.stringify(true))
    await getAbilities() 
    await router.push({ name: 'issues.list' }) 
}

Step 07: Now with casl/vue wrapper with global instances of permissions set for logged in user and using can() method changing edit/delete links to hide/show inside Index.vue of issues

<script setup>
...
import { useAbility } from '@casl/vue'

....
const { can } = useAbility()

// inside template 
<td>
    <router-link v-if="can('issues.update')"  :to="{ name: 'issues.edit', params: { id: issue.id } }">Edit</router-link>
    <a href="#" v-if="can('issues.delete')" @click.prevent="deleteIssue(issue.id)" class="ml-2">Delete</a>
</td>

Now, for editor role, the Delete action will not be shown anymore.

Related Posts


Seeding users and role_user data

Seeding clients and issues data

Adding client column in table

Storing issue in db table

Update issue

Showing user data and logout

Permission securing backend