Skip to content

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.

  1. ability.js: This file will read the abilities from the user-permissions local storage and define the Ability instance using the defineAbility function. In addition to that you can add your custom static abilities here.

  2. CaslHelpers.js: This file provides can 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 and all 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.

COPYRIGHT