Skip to content

Recipes & Integrations

Short, copy-paste answers to the questions that come up most. Each builds on the same model you already have — no extra concepts.

Restrict a User to Their Own Records (ABAC)

Section titled “Restrict a User to Their Own Records (ABAC)”

The classic “an author may edit their own articles, not everyone’s”. Configure ownership once, grant own, and pass the record in the check context — the library enforces it.

const ac = new AccessControl({}, { policy: { ownerField: 'authorId' } });
ac.grant('author').updateOwn('article', ['title', 'body']);
// later, in your handler — load the record first, then check
const article = await db.articles.find(id);
const perm = ac.can('author', { user: { id: userId }, article }).updateOwn('article');
if (!perm.granted) return res.status(403).end();
await db.articles.update(id, perm.filter(req.body)); // only allowed fields

For anything more involved than a field match, use a custom resolver or a .where() condition:

new AccessControl({}, { policy: { owner: (ctx) => ctx.article.authorId === ctx.user.id } });
ac.grant('author').where('$.article.status != locked').updateOwn('article', ['*']);

AccessControl returns the decision; rendering is your app’s job. Compute a plain boolean (or the allowed attributes) and branch on it.

// any framework / template
if (ac.tryCan(role).readAny('dashboard:revenue').granted) {
render(revenueWidget);
}

To drive a whole UI, send the client a small capability map instead of the raw grants:

const caps = {
canEditPost: ac.tryCan(role).updateAny('post').granted,
revenue: ac.tryCan(role).readAny('dashboard:revenue').granted
};
res.json(caps);

There’s nothing React-specific to install — call AccessControl (usually on the server, or wherever your ac instance lives) and consume the booleans.

// pass the capability map (above) down via context
const Caps = React.createContext({});
const useCan = (key) => React.useContext(Caps)[key];
function EditButton() {
return useCan('canEditPost') ? <button>Edit</button> : null;
}

permission.filter() strips a payload (object or array) down to the granted attributes — so a single role definition shapes what each caller sees.

const perm = ac.can(role).readAny('account');
res.json(perm.filter(accounts)); // array → each item filtered

Keep a single source of truth for the model and distribute it; don’t redefine grants per service. The whole model serializes to plain JSON via snapshot() (grants + gates + vocabulary) or getGrantsList() (DB-friendly rows).

// authority service — persist on change
await store.put('policy', JSON.stringify(ac.snapshot()));
// each consumer service — load on boot (and on a change signal)
const ac = new AccessControl().restore(await store.get('policy'));

How you move the blob is your call — all are fine because it’s just JSON:

  • Shared store (Redis/S3/DB row) the services poll or subscribe to — simplest.
  • Config/secret mount redeployed with the fleet — no runtime dependency.
  • A small policy service exposing the snapshot over REST/RPC; consumers cache it.