Appearance
Access control
We use CASL package to provide access control in our template.
CASL may look complex at first so please make sure you first read docs carefully and understand base logic of Access Control to proceed further.
Overview
You can find access control related configuration in src/plugins/casl
directory.
index.ts
: It will read the abilities fromuserAbilityRules
cookie and will defineinitialAbility
usinguserAbilityRules
. then, it will initializeCASL
in our template withinitialAbility
.ability.ts
: It exports theAbility
instance ofCASL
for defining abilities. In typescript version, It also exports types forActions
andSubjects
.shims-ability.d.ts
: This is only available in our TypeScript template. This file is shims file for CASL.useAbility.ts
: This file providesuseAbility
composable for ease so you don't have to importability
fromability.ts
file.
useAbility
By default, CASL provides a useAbility
composable, but using it directly would require us to import a ability
type from ability.ts
file every time we use it. To avoid this, we've defined our custom useAbility
composable in useAbility.ts
file.
Now, whenever we need to use the useAbility
in our templates, we simply import it from useAbility.ts
file.This way, we don't have to worry about importing additional type every time we work with abilities in our application.
Using ACL
If you want show/hide anything based on user's ability, you can use global $can
property.
Let's create a new page with below content:
vue
<script lang="ts" setup>
definePage({
meta: {
action: 'read',
subject: 'AclDemo',
},
})
const user = {
action: 'read' as const,
// `subject` property type is `Subjects` ("src/plugins/casl/AppAbility.ts")
subject: 'Admin' as const,
}
</script>
<template>
<p v-if="$can(user.action, user.subject)">
We have earned 50k more compared to previous week
</p>
<p v-else>
You don't have enough permission to view the finance data
</p>
</template>
vue
<script lang="ts" setup>
definePage({
meta: {
action: 'read',
subject: 'AclDemo',
},
})
const user = {
action: 'read',
subject: 'Admin',
}
</script>
<template>
<p v-if="$can(user.action, user.subject)">
We have earned 50k more compared to previous week
</p>
<p v-else>
You don't have enough permission to view the finance data
</p>
</template>
Updating ability
Using new ability
You will definitely update ability in your app, most probably after login. To update the ability you have to use useAbility
composable like below:
ts
// You will get below object on login's successful API response
const userAbilities = [{
action: 'read',
subject: 'Admin',
}]
// Use composable
const ability = useAbility()
// Update the ability using `update` method
ability.update(userAbilities)
js
import { useAbility } from '@casl/vue'
// You will get below object on login's successful API response
const userAbilities = [{
action: 'read',
subject: 'Admin',
}]
// Use composable
const ability = useAbility()
// Update the ability using `update` method
ability.update(userAbilities)
That's all for updating ability. However, this ability update will get lost on page reload. To persist ability between page reload/close, you have to add ability to cookies:
ts
// You will get below object on login's successful API response
const userAbilities = [{
action: 'read',
subject: 'Admin',
}]
// Use composable
const ability = useAbility()
// Update the ability using `update` method
ability.update(userAbilities)
// Adding ability to cookies so template can pick it up on page reload
useCookie('userAbilityRules').value = userAbilityRules
js
// You will get below object on login's successful API response
const userAbilities = [{
action: 'read',
subject: 'Admin',
}]
// Use composable
const ability = useAbility()
// Update the ability using `update` method
ability.update(userAbilities)
// Adding ability to cookie so template can pick it up on page reload
useCookie('userAbilityRules').value = userAbilityRules
WARNING
When you store user ability in cookies, cookie key name must be userAbilities
. If you want to use different key name, make sure it is same as you have used in src/plugins/casl/index.ts
for retrieving user ability.
Resetting ability
On logout we need to reset ability. Resetting ability is same as updating to new ability, only difference is that we will pass empty array.
ts
// Use composable
const ability = useAbility()
// Update the ability using `update` method
ability.update([])
// Remove "userAbilities" from cookie
useCookie('userAbilityRules').value = null
Route Protection
We have configured router.beforeEach
hook so users can only visit the route they have ability to. You can check its source code in src/router/index.ts
file.
For protecting routes based on ability, all you have to do is add meta to definePage
macro block.
In How to create a new page guide we created a dashboard analytics page. Let's add action
& subject
meta to this route by updating the file as below:
vue
<script lang="ts" setup>
definePage({
meta: {
action: 'read',
subject: 'Web',
},
})
</script>
<template>
<p>This is analytics page inside dashboard directory.</p>
</template>
vue
<script lang="ts" setup>
definePageMeta({
action: 'read',
subject: 'Web',
})
</script>
<template>
<p>This is analytics page inside dashboard directory.</p>
</template>
This will only allow user to view the analytics dashboard if user have above mentioned ability.
Yes, That's it. All you need is these two meta for preventing access to your private route.
Omitting Defining action
& subject
for route
You might have noticed that we haven't defined action
& subject
meta for pages/routes. This is because in our demo, admin user have below ability specified:
js
{
action: 'manage',
subject: 'all',
}
manage
and all
are special keywords in CASL. manage represents any action and all represents any subject.
If user have above mentioned ability and you don't define action
& subject
meta for route, then user can still access all routes.
Hence, we omitted defining action
and subject
meta for pages/routes in our source code.
Show/Hide navigation items
Our template allows hiding & showing navigation items based on user ability.
When you define navigation items in src/navigation/{vertical|horizontal}/index.ts
along with properties like title
, you can write action
& subject
for hiding item if user don't have enough ability.
ts
import type { VerticalNavItems } from '@layouts/types'
export default [
{
title: 'Home',
icon: 'i-mdi-home',
to: 'index',
action: 'read',
subject: 'Post',
},
] as VerticalNavItems
js
export default [
{
title: 'Home',
icon: 'i-mdi-home',
to: 'index',
action: 'read',
subject: 'Post',
},
]
Omitting defining resource and action for nav groups
You can optionally define resource & action on navigation groups.
Navigation groups are intelligently hide and show them selves based on their children.
NOTE
"can be viewed" means ACL ability is resolved to true and user can view it. "hidden" means ACL ability is resolved to false and user can't view it.
- If group can be viewed can any of it's child can also be viewed then group will be visible.
- If Group can be viewed and all it's children are hidden then group also got hidden.
- If Group can't be viewed then in all cases group and it's children will be hidden. It will be hidden even if any of or all of it's children can be viewed.
CAVEAT
As header in vertical navigation menu is independent of it's child items if you want to hide header if all it's below items are hidden then you need to specify resource & action as well with other following items.
Related Pages