Part 4 · 2 chapters · ~12 min

RBAC, ABAC and ReBAC

Role-based, attribute-based and relationship-based access control, policy engines (OPA, Cedar, Casbin), where and how to enforce checks, broken object level authorisation, the four-eyes principle, and auditing authorisation decisions.

9

Three models and policy engines

code
# OPA / Rego: tellers approve transfers under ₦1M in their own branch
package transfers.approve
default allow := false
allow if {
  input.user.role == "teller"
  input.transfer.amount_kobo < 100000000
  input.transfer.branch_id == input.user.branch_id
  not input.transfer.created_by == input.user.id          # four eyes: not your own transfer
}
RBAC, ABAC, ReBAC
three ways to decide "may this subject do this action on this resource?"
RBACUsers get roles; roles grantpermissions. Simple, auditable.Explodes into many roles forfine-grained needs.ABACRules over attributes of user,resource and context: amount <limit, same branch, businesshours.ReBACPermissions follow relationships:editor of the folder that containsthe doc. Google Zanzibar.policy enginesOPA/Rego, Cedar, Casbin: policiesas code, evaluated outsidebusiness logic.where to checkEvery API request, server-side,close to the data; the UI onlyhides buttons.auditLog decisions with policy versionand inputs for sensitive actions.
swipe the figure sideways, or tap expand for full screen
1/6
RBAC
Role-based access control maps roles (teller, branch manager, auditor) to permissions. It is easy to audit and explain; it struggles when access depends on which resource ("only accounts in my branch").
roles → permissionsstruggles with per-resource rules
10

Broken object level authorisation

code
// the bug: authenticated, but not authorised for THIS account
app.get('/accounts/:id', requireAuth, async (req, res) => res.json(await Accounts.find(req.params.id)));

// the fix: scope every query by the caller's rights
app.get('/accounts/:id', requireAuth, async (req, res) => {
  const acct = await Accounts.findOne({ id: req.params.id, ownerId: req.user.id });   // or an authz check
  if (!acct) return res.sendStatus(404);                                              // 404, not 403: do not confirm existence
  res.json(acct);
});