Skip to Content
React AuthorizationOverview

@evanion/react-acl

Not on npm yet

npm install @evanion/react-acl does not resolve. The package is private: true, so a release run versions and tags it without publishing: npm cannot configure a trusted publisher for a package that does not exist on the registry, and the first version has to go up by hand.

The provider, the four hooks and the typed-policy factory are complete and covered by libs/react-acl/src. Every example in this section is a region of libs/react-acl/examples/ and runs under nx test @evanion/react-acl. Inside this repository the package resolves from source like any other workspace package.

The concept. @evanion/react-acl holds one evaluator, one subject and one clock instant in a React context.

What you get. A button four levels down that calls useCan and names none of the three.

Why you want it. A component passed now by hand reads a different instant from its sibling, and a time-windowed rule then answers two ways on one screen.

How the library gets you there. PolicyProvider takes access, subject and context once, and four hooks read all three.

One provider holds what every hook reads

@evanion/react-acl holds one evaluator, one subject and one instant in a React context. PolicyProvider takes access, subject and context once. useCan, useCanMany, useCanFields and useCapabilities read all three out of that context and call the core’s method of the same name.

Baize, the board game shop, draws an Edit button four components below its listing route, and that button asks with three strings and the row it is drawing.

One PolicyProvider holds the evaluator, the shopper and the instant. ShopPage, ListingView and ActionBar pass none of the three, and EditControl reads all three out of context when it calls useCan.

Without the provider, access, subject and now arrive as props on ShopPage, ListingView and ActionBar, none of which read them. A component that threads access and forgets now reads every time window in its branch against a different instant.

'use client'; import { } from '@evanion/react-acl'; type = { : string; : string; : 'draft' | 'published' }; export function ({ }: { : }) { const = ('listing', 'edit', ); if (!.) return null; return < ="button">Edit listing</>; }

EditControl names the object kind, the action and the listing. The shopper and the instant are in the provider above it, and decision is the same decision can returns on a server, with the same allowed and reason on it.

A decision in a browser enforces nothing

Every hook here toggles what a reader sees. A hidden Edit button is still a URL, and the shopper can post to it. Enforcement happens on the trusted side of a boundary, in every layer, each one deciding for itself. Which side decides is the short version and the security contract is the whole of it.

When to reach for react-acl

@evanion/react-acl suits three shapes of app:

  • A client tree that asks about permissions at more than one depth, where access and subject have started arriving as props in components that do not read them.
  • A React Router 8 or Next.js app that already sends the matrix to the browser and wants the client half to read it without rebuilding an evaluator per component.
  • A screen whose shape is the answer: a form whose inputs are separately writable, a menu whose entries are whatever the shopper may reach, a table of rows each with its own answer, an action bar that changes when the row changes.

When react-acl is the wrong tool

@evanion/react-acl answers nothing useful in three places:

  • A server component, a loader or a Server Action. Those call access.can directly. The package carries 'use client' at the top of its entry point and calls createContext, so it cannot run in a React Server Component at all.
  • One component asking one question. A provider for a single call site is a context you have to keep mounted, and access.can(subject, key, action, row) is the same answer in one line.
  • Anywhere the answer has to hold. The hooks read a copy of the matrix that is as fresh as the render that delivered it, and a browser evaluating a stale one keeps granting what the server has revoked.

What the react-acl package exports

@evanion/react-acl exports six values and three types:

ExportWhat it is
PolicyProviderholds access, subject and context for the tree under it
useCan(key, action, object?)one decision
useCanMany(key, action, objects)a decision per row, parallel to the array
useCanFields(key, action, object, axis, proposed?)the field-level decision on one axis
useCapabilities()every action-level decision for the current subject
createPolicyContext(access)a provider and the same four hooks, at a typed policy’s types
PolicyProviderPropsthe provider’s props, for a wrapper that names them
PolicyContextwhat createPolicyContext returns
BoundPolicyProviderPropsthe typed provider’s props, where access is optional

@evanion/react-acl re-exports every core type those signatures name under the same name: Decision, FieldDecision, Access, Subject, Matrix and the rest. The core’s values stay in @evanion/acl, so import hydratePolicy and policy from there.

React 18 or 19 is the only peer dependency and @evanion/acl is a dependency. The package is ESM only.

Where to go next

  • Getting started — mount the provider, read a decision, and drive a tree from the role switch
  • Which side decides — what crosses a server render, and what a hook is allowed to be trusted for
  • API reference — the provider, the four hooks, the factory that keeps a typed policy’s keys, and what each one memoises on

The rules themselves are the core’s:

Last updated on