This example explores how to implement Role-Based Access Control (RBAC) in a React application using CASL. Through a ToDo app example, it demonstrates defining roles, resources, and actions, creating permission structures, and using CASL's Ability to enforce rules. You'll also learn how to wrap components with Can for fine-grained control and dynamically update permissions based on user roles.
I’ve already talked about authorization models and authorization with React CASL. In this post, I’d like to deepen this topic a bit more
In my previous 2 blog posts, I explained Authorization Models and how we can do Routing level authorization with React CASL.
Authorization Models React Project Architecture 19 — (Authorization)
In this example, I will explain how we can authorize the components of the application through ToDO App.
In this example we have users in different roles using our ToDo application, it is actually similar to the example in the previous post;
export const AUTHORIZATION_ROLES = {
SUPER_ADMIN: "SuperAdmin",
ADMIN: "Admin ",
STANDART_USER: "User",
};
And let’s define the Subject/Resource that this will work on
export const AUTHORIZATION_RESOURCES = {
TODO: "Todo",
};
And let’s list the operations we can perform on this Resource.
export const AUTHORIZATION_ACTIONS = {
CREATE: "create",
READ: "read",
UPDATE: "update",
DELETE: "delete",
};
What I’m going to do next is to create the Ability/Permission Structure. I’m here
import { AbilityBuilder, Ability, AbilityClass } from "@casl/ability";
import { AUTHORIZATION_ROLES as roles } from "./roles";
import { AUTHORIZATION_ACTIONS as actions } from "./actions";
import { AUTHORIZATION_RESOURCES as resources } from "./resources";
export type AppAbility = Ability<[Actions, Subjects]>;
export const appAbility = Ability as AbilityClass<AppAbility>;
export default function defineRulesFor(role: string) {
const { can, rules } = new AbilityBuilder(appAbility);
if (role === roles.SUPER_ADMIN) {
can(actions.DELETE, resources.TODO);
can(actions.CREATE, resources.TODO);
can(actions.UPDATE, resources.TODO);
can(actions.READ, resources.TODO);
} else if (role === roles.ADMIN) {
can(actions.CREATE, resources.TODO);
can(actions.UPDATE, resources.TODO);
can(actions.READ, resources.TODO);
} else if (role === roles.STANDART_USER) {
can(actions.READ, resources.TODO);
}
return rules;
}
export function buildAbilityFor(role: string): AppAbility {
return new appAbility(defineRulesFor(role), {
// https://casl.js.org/v5/en/guide/subject-type-detection
detectSubjectType: (object) => object!.type,
});
}
Now after these we wrap them with buttons or the Can or Cannot ability on the components we want.
<Can do={AUTHORIZATION_ACTIONS.DELETE} on={AUTHORIZATION_RESOURCES.TODO}>
<ODButtons.DeleteBulkButton
entityName="Task"
propName="Taskname"
selectedEntities={selectedEntities}
handleDeleteBulk={(deleteTasksIds: string[]) => {
mutation.mutate(deleteTasksIds);
}}
/>
</Can>
Of course, don’t forget to update the Ability when you change roles.
const ability = useContext(AbilityContext);
ability.update(defineRulesFor(updatedRole);