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 checkconst 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 fieldsFor 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', ['*']);Show / Hide UI by Permission
Section titled “Show / Hide UI by Permission”AccessControl returns the decision; rendering is your app’s job. Compute a plain boolean (or the allowed attributes) and branch on it.
// any framework / templateif (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 contextconst Caps = React.createContext({});const useCan = (key) => React.useContext(Caps)[key];
function EditButton() { return useCan('canEditPost') ? <button>Edit</button> : null;}Filter API Responses to Allowed Fields
Section titled “Filter API Responses to Allowed Fields”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 filteredOne Policy Across Microservices
Section titled “One Policy Across Microservices”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 changeawait 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.