npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

casbin-client

v0.2.1

Published

Lightweight client for Casbin

Readme

casbin-client

casbin-client is a library which facilitates manipulation, management, and storage of user permissions in a frontend application for the purposes of authorization. It supports various access control policies, like RBAC, ABAC, ACL, etc.

It is primarily a library for Casbin and strives to be a more modern and polymorphic alternative to the official Casbin.js client library; it is a complete rewrite from the ground up, sharing zero code with its predecessor. It can and will work without any dependencies, however, so having any knowledge of Casbin is entierly optional.

// simple
const user = { can: authorizer(() => permissions) };

user.can('read', 'data');

// with caching
const user = createAuthorizer(() => permissions, { store: sessionStorage });
// and/or promises
const user = createAuthorizer(Promise.resolve(permissions), { store: sessionStorage });

user.can('read', 'data');

// with full casbin model and policy parsing
const user = createAuthorizer(() => (
  fromPolicySource(policy, { parseExpression })
), { store: sessionStorage });

user.can('read', 'data');

| Feature | casbin-client | casbin.js | |---------|-----------------|-------------| | 🌟 Modern tech-stack and dev practices | ✅ TypeScript, DI, FP | 🥀 Babel, OOP | | 🏝️ Less external dependencies | ✅ Zero-dependencies version available | 🥀 Mandatory axios, babel, casbin-core | | 💻 Ergonomic development experience | ✅ Import and use how you like | 🥀 Use in compliance with assumptions hidden in source code | | 🪄 Support for various runtime modes | ✅ Supports both regular (sync) and async modes | 🥀 Every method is async | | 🪶 Lightweight and tree-shakeable | ✅ 0.5KB↔8KB, take what you need | 🥀 90KB+, no tree-shaking | | 🔌 Extendable | ✅ Pluginable at every step | 🥀 Depend on implementation details | | 🤝 Type-safe | ✅ Use typed policies to enforce type safety | 🥀 Untyped strings only | | 🌐 Environment-independent | ✅ Works in any modern JS environment | 🥀 CommonJS build only | | ⚙️ Reliability | ✅ | 🥀 No tests... | | 🔃 More to come... | | |

Install

npm i casbin-client
# or
bun add casbin-client

Use

Basics

At the centrepoint is the concept of an Authorizer - a singleton that looks at users' permissions and decides if the "user" can or cannot do certain actions:

import { createAuthorizer } from 'casbin-client';

const permissions = {
  read: ['data']
};

const user.can = createAuthorizer(() => permissions);

if (user.can('read', 'data')) {
  console.log('Yay, we can read data!');
}
//...

createAuthorizer takes a simple Permissions factory as its primary argument and provides a semantic interface to read from it. It never modifies or tampers with the original object, acting like a simple view on it.
If permissions need changing, simply update them:

//...
if (!user.can('read', 'users')) {
  console.log('Oops, wrong permissions!');
}

permissions.read = ['data', 'users'];

if (user.can('read', 'users')) {
  console.log('Yay, we can read users!');
}

And that's the basics!

Modules

There are 5 isolated modules:

Each module is independent from others, and thus very has little effect on the final bundle size of your application.

authorizer

As covered in the basics section, casbin-client exports a simple createAuthorizer function, with some helper types.
But what if even this is too much?
Enter, casbin-client/core:

import { authorizer, type Permissions } from 'casbin-client/core';

const permissions = {
  read: ['data', 'users'] as const
} satisfies Permissions; // enables full autocomplete

const can = authorizer(() => permissions);

if (can('read', 'data')) {
  console.log('Yay, we can read data!');
}
// Logs "Yay, we can read data!"

It accepts a simple AuthorizerOptions object as its second argument:

import { type AuthorizerOptions } from 'casbin-client/core';

const options = {
  fallback: (action, object) => object !== 'database' && action !== 'delete',
  // A fallback function to resolve missing permissions

  matchAction: (action, source) => source?.[action],
  // A matcher for actions in permissions (default value)

  matchObject: (object, objects) => objects?.includes(object),
  // A matcher for objects in actions (default value)
};

const can = authorizer(() => permissions, options);

if (can('delete', 'database')) {
  console.log('We are doomed!');
} else {
  console.log('Phew, we are safe.');
}
// Logs "Phew, we are safe."

createAuthorizer

This is a much more versatile factory function.
It allows automatic caching using the Storage API and working with promises.

createAuthorizer also accepts two arguments:

  • a Permissions object:
    import { type Permissions } from 'casbin-client';
    
    const permissions: Permissions = {
      read: ['data', 'users']
    };
  • and customization options (optional)
    import { type SyncAuthorizerOptions } from 'casbin-client';
    
    const options: SyncAuthorizerOptions = {
      store: sessionStorage,
      // A `Storage` object to use as a cache for permissions
    
      key: 'auth',
      // A unique key to store the permissions in the store
    
      fallback: (action, object) => object !== 'database' && action !== 'delete',
      // A fallback function to resolve missing permissions
    };

And allows for simple permission checking:

import { createAuthorizer } from 'casbin-client';

const user = createAuthorizer(() => permissions, options);

if (user.can('delete', 'database')) {
  console.log('We are doomed!');
} else {
  console.log('Phew, we are safe.');
}
// Logs "Phew, we are safe."

Note

The .can method always re-runs the permission factory!
In reactive UI-frameworks it is advised to wrap its calls with a computed primitive, like useMemo or computed.

Async mode

Note

This mode is not for usage with reactive UI frameworks like react, solid, or vue.
In the context of reactive data in UI components, it's better to use createAuthorizer in combination with reactive primitives like useQuery, createResource, or computed.

The "Async mode" is for the case when there's no way to use a reactive primitive and the execution context is synchronous.

createAuthorizer makes it easy to work with promises, because the permissions factory can also be a promise:

const permissionsUrl = 'https://raw.githubusercontent.com/Raiondesu/casbin-client/refs/heads/main/examples/permissions.json';
const remotePermissions = fetch(permissionsUrl).then(r => r.json());

createAuthorizer simply treats the promise as a factory:

const user = createAuthorizer(remotePermissions, options);

// ...
// some time later in a file far far away
if (user.can('read', 'data')) {
  console.log('Yay, we can read data!');
}

In the context of a single function this is, of course, not possible, so the promise is proxied and can be awaited separately:

await user.remote;

if (user.can('read', 'data')) {
  console.log('Yay, we can read data!');
}

Typing

Both authorizer and createAuthorizer accept a generic parameter, which can be automatically inferred from permissions:

type MyPermissions = {
  read: ['data']
};

const permissions: any = {
  read: ['data']
};

const auth = createAuthorizer<MyPermissions>(() => permissions);

// Full autocomplete and type checking!
auth.can('read', 'data');

casbin-client/model

Allows to parse and use a Casbin model.

import { parseModel } from 'casbin-client/model';

const model = `
  [request_definition]
  r = sub, obj, act

  [policy_definition]
  p = sub, obj, act

  [role_definition]
  g = _, _

  [policy_effect]
  e = some(where (p.eft == allow))

  [matchers]
  m = r.obj == p.obj && r.act == p.act && g(r.sub, p.sub)
`;

const parsed = parseModel(model);

console.log(parsed.matchers.m({
  r: { sub: 'alice', act: 'read', obj: 'data' },
  p: { sub: 'reader', act: 'read', obj: 'data' },
  g: (r, p) => 'alice' === r && 'reader' === p,
  ...parsed.matchers,
  ...parsed.policyEffect
}));
//> true

casbin-client/policy

Allows to parse and use a Casbin model with a Casbin policy.

This module implements the most essential sub-set of read-only features from casbin-core.

See the list of missing features to gauge if this is useful for your project.

import { createAuthorizer } from 'casbin-client';
import { fromPolicySource } from 'casbin-client/policy';

const model = `
  [request_definition]
  r = sub, obj, act

  [policy_definition]
  p = sub, obj, act

  [role_definition]
  g = _, _

  [policy_effect]
  e = some(where (p.eft == allow))

  [matchers]
  m = r.obj == p.obj && r.act == p.act && g(r.sub, p.sub)
`;

// Result from `CasbinJsGetUserPermission` or otherwise manually loaded
const policy = {
  g: [
    ["g", "alice", "reader"],
    ["g", "alice", "writer"],
    ["g", "bob", "reader"],
    ["g", "cathy", "admin"],
  ],
  m: model,
  p: [
    ["p", "reader", "data", "read"],
    ["p", "writer", "data", "write"],
    ["p", "admin", "data", "delete"],
  ]
};

// Note that this is a costly function to call
const permissions = fromPolicySource(policy);

const user = createAuthorizer(() => permissions);

if (user.can('read', 'data')) {
  console.log('Yay, we can read data!');
}

const alicePermissions = fromPolicySource(policy, {
  request: ['r', 'alice']
});

const alice = createAuthorizer(() => alicePermissions);

if (alice.can('read', 'data')) {
  console.log('Yay, alice can read data!');
}

if (!alice.can('delete', 'data')) {
  console.log('Nope, alice cannot delete data!');
}

casbin-client/parser

This module uses modified subscript with a subset of justin syntax.

import { parseExpression } from 'casbin-client/parser';

const run = parseExpression('"a" in b && b.a() === true');

console.log(run({ b: { a: () => true } }));
//> true

It can be passed into model and policy parsers as options, in order to enable complete Casbin experience in JS:

const reader = fromPolicySource(policy, {
  request: ['r', 'bob'],
  parseExpression,
});

const bob = createAuthorizer(() => reader);

if (bob.can.read('data')) {
  console.log('Yeah, Bob can read');
}

Why

Casbin is amazing for dynamic and polymorphic control of user access. But the official client-side library left a lot to be desired. Being a de-facto extension on the casbin-core package for Node.js, it brings in a lot of unneeded dependencies and wraps them in an API that is awkward to use in a modern JS ecosystem.

Roadmap / TODO list

  • [x] Process simple policies ({ write: ['data'], read: ['data'] })
  • [x] Custom storage or DB providers for caching
  • [x] Simple integration with any network/query client
  • [x] Ability to check user permissions using policies and model matchers
  • [x] Ability to parse permissions from policies without the baggage of matchers and effects
  • [ ] Integrations for popular frontend frameworks
  • [ ] Generate ambient types from policy csv or permissions json
  • [ ] Parse permissions at the type level from policy source
  • [ ] Reliable error reporting
  • [ ] Support for complex pattern-matching (/data/*, keyMatch(...))
  • [ ] Support for internal eval(...) and other built-in functions
  • [ ] Support for custom matcher contexts
  • [ ] Support for effect expressions
  • [ ] Full test coverage

Feel like something's missing? Submit an issue!
Wanna help? Fork and submit a PR!

Security notice

Parsing model configuration leads to evaluation of user-provided expressions, which can lead to unsafe behavior. Refrain from using arbitrary model parsing on the client-side to avoid potential security risks!

Note
Despite lacking a similar warning, Casbin.js has the same potential for introducing vulnerabilities.

Contributing

Prerequisites:

  • bun:
    curl -fsSL https://bun.sh/install | bash
    powershell -c "irm bun.sh/install.ps1 | iex"

To install dependencies:

bun install

Build

bun run build

Test

bun run test