Skip to content

Express Integration

A small middleware factory covers most REST handlers: it loads the record (for own checks and conditions), checks access fail‑closed, and exposes the resolved permission so the handler can filter its response.

import { AccessControl } from 'accesscontrol';
const ac = new AccessControl(grants, {
policy: { ownerField: 'ownerId' },
context: { env: process.env.NODE_ENV }
});
function authorize(action, resource, loadRecord) {
return async (req, res, next) => {
try {
const record = loadRecord ? await loadRecord(req) : undefined;
const ctx = { ip: req.ip, user: req.user, [resource]: record };
// tryCan: a thrown error becomes a denial, never an accidental allow
const perm = ac.tryCan(req.user.role, ctx).action(action, resource);
if (!perm.granted) return res.status(403).end();
req.permission = perm;
next();
} catch {
res.status(403).end();
}
};
}

Use it per route; filter the response down to the granted attributes:

router.get(
'/articles/:id',
authorize('read:any', 'article'),
async (req, res) => {
const article = await db.findArticle(req.params.id);
res.json(req.permission.filter(article));
}
);
router.patch(
'/orders/:id',
authorize('update:own', 'order', (req) => db.getOrder(req.params.id)),
async (req, res) => {
const data = req.permission.filter(req.body); // strip disallowed fields
res.json(await db.updateOrder(req.params.id, data));
}
);

A fuller, runnable version (ownership, custom actions, audit) lives in the repository’s examples/express-middleware.example.ts.