expresstutorial
Tutorial: guarding an Express route with rbac-fs
This is runnable as-is: node examples/07-express-middleware.mjs, then hit it with curl from another terminal, or just read the fetch calls baked into the script.
The full example
import express from 'express';
import { RBAC } from 'rbac-fs';
import { rbacMiddleware } from 'rbac-fs/express';
const rbac = new RBAC({ tenantId: 'acme-corp' });
await rbac.createRole('manager', { permissions: [{ resource: 'invoice', actions: ['approve'] }] }, { force: true });
const app = express();
// Stand-in for your real auth middleware — rbac-fs never assumes how a
// user got attached to the request, only that it's there by the time
// rbacMiddleware runs.
app.use((req, _res, next) => {
const role = req.header('x-user-role');
if (role) req.user = { id: 'demo-user', role };
next();
});
app.post(
'/invoices/:id/approve',
rbacMiddleware(rbac, 'invoice', 'approve'),
(req, res) => res.json({ approved: req.params.id }),
);
app.listen(4001);Three requests, three outcomes
curl -X POST http://localhost:4001/invoices/inv-1/approve -H 'x-user-role: manager'
# 200 { "approved": "inv-1" }
curl -X POST http://localhost:4001/invoices/inv-1/approve -H 'x-user-role: viewer'
# 403 — viewer role has no invoice:approve permission
curl -X POST http://localhost:4001/invoices/inv-1/approve
# 403 — no x-user-role header, so req.user was never setThe auth-middleware ordering that matters
rbacMiddleware reads req.user — it does nothing to populate it. The header-reading middleware in this example stands in for your real auth layer (Passport, a JWT decode, a session lookup); in a real app, that middleware must run *before* rbacMiddleware in the chain, or every request will 403 with no user to check against.
Verified against examples/07-express-middleware.mjs, which runs this exact sequence against a live server on port 4001.