Skip to content
Advertisement

CASL Authorisation in a React app “Can” conditions have no effect

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.

Example on Code Sandbox

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.

Subject Type Detection

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”.

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement