Appearance
Access control
We use CASL package to handle Access Control in our project.
On this page, we will explore the process of setting up CASL.
WARNING
Please read the CASL documentation carefully and grasp the basic concept of Access Control before proceeding.
Integrating CASL in the Application
You can find access control-related configuration in the src/plugins/casl
directory.
ability.js
: This file will read the abilities from theuser-permissions
local storage and define theAbility
instance using thedefineAbility
function. In addition to that you can add your custom static abilities here.CaslHelpers.js
: This file providescan
component to check whether a user is authorized to perform a specific action.
Define Rules
File:
ability.js
js
import { defineAbility } from "@casl/ability";
import { jsonParser , AbilityBuilder} from "@/@core/utils/helpers.js";
const userAbilities = jsonParser(localStorage.getItem("user-permissions"));
const generatedUserAbilities = AbilityBuilder(userAbilities);
export default defineAbility((can, cannot) => {
can("manage", "all"); // For Demo Purpose
if (generatedUserAbilities && generatedUserAbilities.length) {
generatedUserAbilities.map((item) => {
can(item.action, item.subject);
});
}
});
You can add your action and subject with different conditions here.
<Can>
Component
You can use the Can
component if you want to show/hide content based on the user's permissions:
jsx
import {Can} from "@/plugins/Casl/can";
export default () => {
return (
<div>
<Can I="read" a="Post">
<p>E-commerce sales are predicted to have grown by 5.2% this month.</p>
</Can>
<Can not I="read" a="Post">
<p> Access Denied: You are not authorized to access the data.</p>
</Can>
</div>
);
};
useAbility
If the logic within a component is complex or difficult to manage, it may not be suitable to use the <Can>
component. In these instances, the useAbility
hook is a suitable alternative.
jsx
import {AbilityContext} from "@/plugins/Casl/can";
export default () => {
const ability = useAbility(AbilityContext);
const removeTodo = () => { /* logic to remove todo form */
};
return (
<div>
{ability.can("remove", "todo") && (
<button onClick={removeTodo}>Remove Todo</button>
)}
</div>
);
};
Updating ability
Whenever a user logs in (and out), we must ensure the Ability instance updates with the new rules.
Create a new Ability instance
Suppose that the server returns the user with specific permissions on login:
jsx
// successful API response 👇
const userAbilities = [
{
action: "create",
subject: "Blog",
}
];
// UseAbility Hook
const ability = useAbility(AbilityContext)
// Update the ability using `update` method 👇
ability.update(userAbilities)
WARNING
Ability updates will be lost on page refresh. One way to solve this is by using localStorage to store the data:
jsx
// successful API response 👇
const userAbilities = [
{
action: "create",
subject: "Blog",
}
];
// UseAbility Hook
const ability = useAbility(AbilityContext)
// Update the ability using `update` method and store it in local storage
ability.update(userAbilities);
localStorage.setItem("user-permissions", userAbilities)
The key name we use to store data in local storage is user-permissions
.
If you prefer a different key name, ensure it matches the one used in the src/plugins/casl/abilities.js
to access user abilities.
Resetting ability
To reset the ability on logout, pass an empty array to the ability update
method.
js
// UseAbility Hook
const ability = useAbility(AbilityContext)
// Update the ability using `update` method
ability.update([])
// Remove "userAbilities" from local storage
localStorage.removeItem("user-permissions", null )
Protected Routes
When a user opens a new page, the guard is triggered by the React Router to check whether the user is authorized to view the page. This way the user can go to the desired page if they have the ability.
You can inspect src/routes/ProtectedRoutes.jsx
file to see our configured route guard.
Integrating Role-Based Authorization
File:
src/routes/Router.jsx
To apply role-based route guards, wrap your desired route with the <ProtectedRoutes>
component to check if the user has any of the required permissions.
Specify the permissions using the action
and subject
properties:
Here's an example of a route guard:
jsx
<Route element={<ProtectedRoutes action="read" subject="article"/>}>
<Route element={<Articles/>} path="article"/>
</Route>
<Route element={<ProtectedRoutes action={["create", "delete"]} subject="todo"/>}>
<Route element={<Todo/>} path="todo"/>
</Route>
In this example, the ProtectedRoutes
checks if the user has any specified permissions to access the route.
Current Setting
To ensure flexibility and control over the content shown in our demo, we have assigned manage
and all
abilities to our Ability instance. Since there are no specific action
and subject
metadata for our routes, users can access all available routes
You can add your own action
and subject
meta for routes to add route protection.
manage
andall
are special keywords in CASL. manage represents any action and all represents any subject.
Side Navigation Access Control
This way you can restrict users' access to navigation menus based on their ability very easily.
All you have to do is navigate to the src/navigation/index.js
file and add action
, and subject
properties to the nav item.
js
export default [
{
title: "Email",
icon: "Sms",
to: "apps/email",
action: "read",
subject: "email",
},
]
Users can now see the Email
item on the sidebar if they can read
the email
.
Visible or Hidden ?
Navigation groups are designed to automatically show or hide themselves based on their children. However, you can also specify resources and actions for them if desired.
Note
Hidden: This indicates that the ACL returned false, meaning the user does not have the ability to view the item.
Can be viewed: This indicates that the ACL returned true, allowing the user to view the item.
✅ A navigation group is visible if both the group and its sub-items can be viewed.
✅ A navigation group is hidden if the group can be viewed, but its sub-items are hidden.
✅ A navigation group is hidden if the group itself is hidden, regardless of whether its sub-items can be viewed or are hidden.