I am trying to implement CASL Authorisation in a react app, I think there is something I am not quite understanding about how to implement it.
The standard Can components seems to work with the basic CRUD actions, but I have not been able to get the conditions to have any effect. I think I am missing something.
My current theory is that I need to be using TypeScript instead of plain Javascript to make the whole thing work. I don’t know any TypeScript at the moment and I really want to push forward with my App instead of having to learn another language. I will learn TypeScript if I have to though, I need to know if its worth doing. Below is a boiled down version of what I have built so far.
Expected Behaviour
I would expect the app to show that the person can read and create Thing records. They should also be able to update or delete the specific Apple record.
Expected Output:
I can look at things
I can create a thing
I can update this apple
I can delete this apple
Actual Behaviour
It ignores anything to do with the conditions and allows create, read, update and delete on everything.
Actual Output:
I can look at things
I can create a thing
I can update any thing
I can delete any thing
The main app
import "./styles.css"; import ThingManager from "../components/ThingManager"; import AbilityContextComponent from "../components/AbilityContextComponent"; export default function App() { return ( <div className="App"> <AbilityContextComponent> <ThingManager /> </AbilityContextComponent> </div> ); }
Ability Context Component For building the ability context and wrapping up the generation of abilities
import React from "react"; import { AbilityContext } from "../src/Can"; import { defineAbility } from "@casl/ability"; class AbilityContextComponent extends React.Component { render() { const ability = defineAbility((can) => { can("read", "thing"); can("create", "thing"); can("update", "thing", { userId: 3 }); can("delete", "thing", { userId: 3 }); }); return ( <AbilityContext.Provider value={ability}> {this.props.children} </AbilityContext.Provider> ); } } export default AbilityContextComponent;
The Can component is generated here
import { createContextualCan } from "@casl/react"; import React from "react"; export const AbilityContext = React.createContext(); export const Can = createContextualCan(AbilityContext.Consumer);
Finally a component where authorisation on “Thing” might be used
import React from "react"; import { Can } from "../src/Can"; class ThingManager extends React.Component { render() { const thing = { Name: "Apple", Description: "this is an Apple", Colour: "Green", UserId: 3 }; return ( <div> <h3>Manage your things here</h3> <Can I="read" a="thing"> <p>I can look at things</p> </Can> <Can I="create" a="thing"> <p>I can create a thing</p> </Can> <Can I="update" a="thing"> <p>I can update any thing</p> </Can> <Can I="delete" a="thing"> <p>I can delete any thing</p> </Can> <Can I="update" this={thing}> <p>I can delete this {thing.Name}</p> </Can> <Can I="delete" this={thing}> <p>I can delete any {thing.Name}</p> </Can> </div> ); } } export default ThingManager;
Advertisement
Answer
What I was looking for was information on how to change the “Subject” which is CASL’s term for the thing you are trying to set rights on. In this instance, what I am calling “thing”.
It turns out there are many ways to detect the subject type.
Generally they all involve telling the Can component what the subject of the incoming object is.
The basic method is to call the function subject on the object you are passing to “this”.
Import subject from “@casl/ability” and call it like subject(“thing”, apple)
Working version on CodeSandBox
import React from "react"; import { Can } from "../src/Can"; import { subject } from "@casl/ability"; class ThingManager extends React.Component { render() { const apple = { Name: "Apple", Description: "this is an Apple", Colour: "Green", UserId: 3 }; return ( <div> <h3>Manage your things here</h3> <Can I="read" a="thing"> <p>I can look at things</p> </Can> <Can I="create" a="thing"> <p>I can create a thing</p> </Can> <Can I="update" a="thing"> <p>I can update a thing</p> </Can> <Can I="delete" a="thing"> <p>I can delete a thing</p> </Can> <Can I="update" this={subject("thing", apple)}> <p>I can delete this {apple.Name}</p> </Can> <Can I="delete" this={subject("thing", apple)}> <p>I can delete any {apple.Name}</p> </Can> </div> ); } } export default ThingManager;
I had also gotten the theory of how Can works a bit wrong. You will notice in the working example that the “I can update a thing” and the “I can delete a thing” texts are still showing. This is because the general “Can I update a” and “Can I delete a” type components all show their children as long as the user can update/delete at least one thing. Its not saying “this user has access to everything”.