@casl/ability vs @casl/react vs accesscontrol vs casbin
Frontend Authorization and Access Control Libraries
@casl/ability@casl/reactaccesscontrolcasbin

Frontend Authorization and Access Control Libraries

@casl/ability, @casl/react, accesscontrol, and casbin are JavaScript libraries designed to manage user permissions and enforce access control in web applications. They help developers define what actions users can perform on specific resources, and integrate those rules into UI rendering or API logic. While all aim to solve authorization, they differ significantly in architecture, syntax, ecosystem integration, and frontend suitability.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
@casl/ability06,865360 kB322 months agoMIT
@casl/react06,86539.8 kB322 months agoMIT
accesscontrol02,281-498 years agoMIT
casbin02,877666 kB15a month agoApache-2.0

Frontend Authorization Compared: CASL, AccessControl, and Casbin

Managing what users can see or do in your app is critical — but doing it cleanly without cluttering your components is hard. The libraries @casl/ability, @casl/react, accesscontrol, and casbin each offer different approaches to this problem. Let’s compare how they define rules, check permissions, and integrate into real frontend code.

🔐 Defining Permissions: Natural Language vs JSON vs Policy Files

@casl/ability uses a fluent, human-readable API to define what a user can do.

// Define abilities using AbilityBuilder
import { AbilityBuilder, createMongoAbility } from '@casl/ability';

const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
can('read', 'Post');
can('update', 'Post', { authorId: userId });
cannot('delete', 'Post');
const ability = build();

This style feels intuitive and maps closely to business requirements like “editors can update their own posts.”

accesscontrol defines permissions as static JSON-like objects grouped by roles.

// Define grants as an object
import AccessControl from 'accesscontrol';

const ac = new AccessControl({
  admin: {
    post: ['create', 'read', 'update', 'delete']
  },
  editor: {
    post: ['read', 'update']
  }
});

// Or add dynamically
ac.grant('editor').execute('update').on('post');

It’s great if your roles and permissions are fixed and you want to load them from config.

casbin uses external policy files (or arrays) with a generic format like [sub, obj, act].

// Define policy in code (usually loaded from file/db)
import { newEnforcer } from 'casbin';
import { MemoryAdapter } from 'casbin';

const adapter = new MemoryAdapter([
  ['admin', 'post', 'read'],
  ['admin', 'post', 'write'],
  ['editor', 'post', 'read']
]);
const enforcer = await newEnforcer('model.conf', adapter);

You also need a model file (model.conf) that defines the matching logic. This is powerful for complex systems but overkill for most frontend apps.

⚠️ Note: casbin’s JavaScript version is not optimized for frontend use. It includes large dependencies and assumes server-side execution. Avoid it in browser bundles unless absolutely necessary.

✅ Checking Permissions in Code

How do you actually test if a user can perform an action?

@casl/ability offers a simple method:

// Check anywhere
if (ability.can('update', post)) {
  // Show edit button
}

The second argument can be a class, string, or object — and it supports field-level and conditional checks.

accesscontrol uses role + resource + action triples:

// Check permission for a role
const permission = ac.can('editor').execute('update').on('post');
if (permission.granted) {
  // Allow action
}

Note: You must know the user’s role ahead of time. There’s no built-in concept of a “current user” — you manage that yourself.

casbin checks using subject, object, and action:

// Enforce policy
const allowed = await enforcer.enforce('editor', 'post', 'update');
if (allowed) {
  // Proceed
}

Again, this works but brings unnecessary overhead to the frontend.

🧩 React Integration: Reactive UI Updates

Only @casl/react provides first-class React support.

// Use the <Can> component
import { Can } from '@casl/react';
import { useAbility } from '@casl/react';

function PostActions({ post }) {
  return (
    <>
      <Can I="update" a="Post" of={post}>
        <button>Edit</button>
      </Can>
      <Can I="delete" a="Post">
        <button>Delete</button>
      </Can>
    </>
  );
}

// Or use the hook
function useCanEdit(post) {
  const ability = useAbility();
  return ability.can('update', post);
}

When the underlying ability instance changes (e.g., after login), components using <Can> or useAbility() automatically re-render.

accesscontrol and casbin have no official React bindings. You’d need to wrap their checks in useState/useEffect manually:

// Manual integration with accesscontrol (not ideal)
function PostActions({ post, userRole }) {
  const [canUpdate, setCanUpdate] = useState(false);

  useEffect(() => {
    const permission = ac.can(userRole).execute('update').on('post');
    setCanUpdate(permission.granted);
  }, [userRole]);

  return canUpdate ? <button>Edit</button> : null;
}

This adds boilerplate and doesn’t react to granular changes (like post ownership).

🔄 Updating Permissions at Runtime

User permissions often change during a session (e.g., after role upgrade). How do these libraries handle that?

@casl/ability lets you update rules dynamically:

// Update ability instance
ability.update((can, cannot) => {
  can('publish', 'Post');
});
// All <Can> components re-render automatically

accesscontrol allows adding/removing grants, but won’t notify your UI:

ac.grant('editor').execute('publish').on('post');
// Your React components won’t know unless you trigger a state update manually

casbin supports reloading policies, but again — no frontend reactivity.

📦 Architecture and Bundle Impact

  • @casl/ability: Tiny core (~5KB min+gzip), modular design. Only import what you need.
  • @casl/react: Adds ~1KB on top of @casl/ability for React-specific logic.
  • accesscontrol: ~8KB, includes full RBAC engine but no framework glue.
  • casbin: ~30KB+, includes parser, model evaluator, and adapters — too heavy for most frontend use cases.

🛑 Deprecation and Maintenance Status

As of 2024:

  • @casl/ability and @casl/react are actively maintained, with regular updates and strong TypeScript support.
  • accesscontrol hasn’t had a major release since 2020 but remains stable and functional for basic RBAC. No deprecation notice, but development is slow.
  • casbin’s JavaScript port is community-maintained and lags behind the Go core. The team recommends using it only on the backend.

💡 When to Use What: Real Scenarios

Scenario 1: Admin Dashboard with Role-Based UI

You have roles like “admin”, “editor”, and “viewer”, and want buttons to appear/disappear based on permissions.

  • Best choice: @casl/ability + @casl/react
  • Why? Natural syntax, reactive updates, and minimal boilerplate.

Scenario 2: Static Permission Model Loaded from Config

Your app loads a JSON permission matrix at startup and never changes it during the session.

  • Acceptable choice: accesscontrol
  • Why? Simple setup, no need for reactivity.

Scenario 3: Complex Enterprise Policies (ABAC, Custom Matchers)

You need attribute-based rules like “managers can approve expenses under $10k”.

  • Use Casbin — but only on the backend
  • Why? Frontend should not contain policy logic; instead, call an authz API that uses Casbin server-side.

Scenario 4: Lightweight SPA with Simple Rules

You just need to hide a “Delete” button for non-owners.

  • Best choice: @casl/ability alone (no React package needed if you manage state yourself)

📊 Summary Table

Feature@casl/ability@casl/reactaccesscontrolcasbin
Frontend-optimized✅ Yes✅ Yes (React-specific)⚠️ Partially❌ No (backend-focused)
Reactive UI updates✅ With event emitter✅ Built-in❌ Manual only❌ Manual only
Permission syntaxFluent DSLSame as @casl/abilityJSON/object grantsPolicy files (INI/CSV)
Conditional rules✅ Yes (e.g., { authorId })✅ Yes❌ Role-based only✅ Yes (with model)
Bundle size~5KB~6KB~8KB~30KB+
Maintenance status✅ Active✅ Active⚠️ Stable but slow⚠️ Backend-focused

💎 Final Recommendation

For frontend authorization, CASL is the clear winner. Start with @casl/ability for the core logic, and add @casl/react if you’re in a React app. It gives you expressive rules, tiny footprint, and automatic UI updates.

Avoid casbin in the browser — push complex policies to your backend. Use accesscontrol only if you have a simple, static role model and don’t mind manual UI updates.

Remember: authorization logic should live as close to the data as possible. Even with CASL, always validate permissions on the server — frontend checks are just for UX, not security.

How to Choose: @casl/ability vs @casl/react vs accesscontrol vs casbin

  • @casl/ability:

    Choose @casl/ability if you need a lightweight, flexible, and framework-agnostic core library for defining and checking permissions using a natural, readable DSL (e.g., 'can read Post'). It’s ideal for projects where you want fine-grained control over ability definitions and plan to integrate with React, Vue, Angular, or vanilla JS without heavy dependencies.

  • @casl/react:

    Choose @casl/react if you’re already using @casl/ability and building a React application that requires reactive, component-level permission checks. It provides hooks like useAbility and components like Can that automatically re-render when permissions change, making it seamless to hide or disable UI elements based on user roles.

  • accesscontrol:

    Choose accesscontrol if you prefer a declarative, role-based access control (RBAC) model defined via JSON or object literals, and don’t need real-time reactivity in the UI. It’s well-suited for server-side or static permission setups where roles and grants are known at startup and rarely change during runtime.

  • casbin:

    Choose casbin if your project requires advanced authorization models like ABAC, RBAC, or ACL with support for complex policies stored externally (e.g., in files or databases). However, note that Casbin’s JavaScript port is primarily designed for backend use; using it in frontend apps adds significant bundle size and lacks native React integration, so it’s generally not recommended for client-side-only authorization.

README for @casl/ability

CASL Ability @casl/ability NPM version CASL Documentation Support

This package is the core of CASL. It includes logic responsible for checking and defining permissions.

Installation

npm install @casl/ability
# or
pnpm install @casl/ability
# or
yarn add @casl/ability

Documentation

This README file contains only basic information about the package. If you need an in depth introduction, please visit CASL's documentation.

Getting Started

Note: the best way to get started is to read the Guide in the official documentation. In this README file, you will find just basic information.

Note: all the examples below are written in ES6 using ES modules but CASL also has a sophisticated support for TypeScript, read CASL TypeScript support for details.

1. Define Abilities

Lets define Ability for a blog website where visitors:

  • can read blog posts
  • can manage (i.e., do anything) own posts
  • cannot delete a post if it was created more than a day ago
import { AbilityBuilder, createMongoAbility } from '@casl/ability';
import { User } from '../models'; // application specific interfaces

/**
 * @param user contains details about logged in user: its id, name, email, etc
 */
function defineAbilitiesFor(user) {
  const { can, cannot, build } = new AbilityBuilder(createMongoAbility);

  // can read blog posts
  can('read', 'BlogPost');
  // can manage (i.e., do anything) own posts
  can('manage', 'BlogPost', { author: user.id });
  // cannot delete a post if it was created more than a day ago
  cannot('delete', 'BlogPost', {
    createdAt: { $lt: Date.now() - 24 * 60 * 60 * 1000 }
  });

  return build();
});

Do you see how easily business requirements were translated into CASL's rules?

And yes, Ability class allow you to use some MongoDB operators to define conditions. Don't worry if you don't know MongoDB, it's not required and explained in details in Defining Abilities

2. Check Abilities

Later on you can check abilities by using can and cannot methods of Ability instance.

import { BlogPost, ForbiddenError } from '../models';

const user = getLoggedInUser(); // app specific function
const ability = defineAbilitiesFor(user)

// true if ability allows to read at least one Post
ability.can('read', 'BlogPost');

// true if there is no ability to read this particular blog post
const post = new BlogPost({ title: 'What is CASL?' });
ability.cannot('read', post);

// you can even throw an error if there is a missed ability
ForbiddenError.from(ability).throwUnlessCan('read', post);

Of course, you are not restricted to use only class instances in order to check permissions on objects. See Introduction for the detailed explanation.

3. Database integration

CASL has a complementary package [@casl/mongoose] which provides easy integration with MongoDB and [mongoose].

import { accessibleRecordsPlugin } from '@casl/mongoose';
import mongoose from 'mongoose';

mongoose.plugin(accessibleRecordsPlugin);

const user = getUserLoggedInUser(); // app specific function

const ability = defineAbilitiesFor(user);
const BlogPost = mongoose.model('BlogPost', mongoose.Schema({
  title: String,
  author: mongoose.Types.ObjectId,
  content: String,
  createdAt: Date,
  hidden: { type: Boolean, default: false }
}))

// returns mongoose Query, so you can chain it with other conditions
const posts = await Post.accessibleBy(ability).where({ hidden: false });

// you can also call it on existing query to enforce permissions
const hiddenPosts = await Post.find({ hidden: true }).accessibleBy(ability);

// you can even pass the action as a 2nd parameter. By default action is "read"
const updatablePosts = await Post.accessibleBy(ability, 'update');

See Database integration for details.

4. Advanced usage

CASL is incrementally adoptable, that means you can start your project with simple claim (or action) based authorization and evolve it later, when your app functionality evolves.

CASL is composable, that means you can implement alternative conditions matching (e.g., based on joi, ajv or pure functions) and field matching (e.g., to support alternative syntax in fields like addresses.*.street or addresses[0].street) logic.

See Advanced usage for details.

5. Performance and computational complexity

CASL checks are quite fast, thanks to underlying rule index structure. The estimated complexity of different operations can be found below:

OperationComplexityNotes
Ability creation timeO(n)n - amount of rules
Check by action and subject type (e.g., ability.can('read', 'Todo'))O(1)
Check by action and subject object (e.g., ability.can('read', todo))O(m + k) + O(p)m - amount of rules for the same pair of action and subject; k - amount of operators in conditions; O(p) - complexity of used operators (e.g., $in implementation is more complex than $lt)

Want to help?

Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on guidelines for contributing

License

MIT License