Skip to content

Authorisation — RBAC and ReBAC

Once a user is signed in, the question shifts from "are they who they say they are?" to "what are they allowed to do?". IntelliAuth supports two models: RBAC (role-based access control) and ReBAC (relationship-based access control). Most apps start with RBAC and never need ReBAC; some apps need ReBAC from day one.

The simpler model. Users have roles. Roles carry capabilities. Capabilities gate what your APIs allow.

Alice → role: admin → can: manage applications, read + write everything
Bob → role: editor → can: read everything, write documents
Carol → role: viewer → can: read everything; no writes

At runtime, capabilities are surfaced to your code as scope strings on the access token — that's the wire format. The next code block shows the runtime shape.

This works beautifully when:

  • The authorisation rule is "this kind of user can do this kind of thing."
  • The number of roles is small (3-10) and changes rarely.
  • You don't need per-resource sharing.

RBAC is the common pattern for product permissions. Admin, member, viewer; sometimes more granular (billing-admin, support-engineer, etc.).

The tenant admin defines the roles + their scope sets in the tenant admin console. The user-to-role assignment lives on the identity. When a token is issued, the role's scopes get embedded as the scope claim. Your API reads req.user.scope and decides.

The richer model. Authorisation is computed from the relationship between a user and a specific resource — not from a role.

Examples:

  • "Alice can read document doc:123 because she's a viewer on the folder it lives in."
  • "Bob can edit project proj:42 because his team is on the project's editor list."
  • "Carol can comment on issue issue:9 because she's a commenter on the parent repository, and the repository allows commenters to comment on any issue."

These rules involve specific resources (this doc, this project), specific relationships (viewer, editor, commenter), and inheritance (a permission on a folder flows down to a document). RBAC can't model this cleanly — you'd need a role per (resource × permission) combination, which explodes fast.

ReBAC handles this natively by storing tuples: (user, relation, resource). The platform's authorisation engine traverses the relationship graph to answer "can Alice read doc:123?"

The platform's ReBAC surface is the /api/v1/resources/* + /api/v1/resource-types/* endpoint family. You define resource types (folder, document, project) and the relationships between them; you write tuples as users share things; you ask the API "does this user have this permission on this resource?"

This is Pro-tier and above on the platform — see Plans overview.

A rough decision tree:

  • Your authorisation rules are "role X can do thing Y." → RBAC.
  • You have user-resource sharing — Alice shares doc with Bob, Bob shares with Carol, who can read what? → ReBAC.
  • You have role-based defaults plus exceptions per resource. → RBAC for the defaults, ReBAC for the exceptions. The two can coexist.

You can absolutely build ReBAC from scratch (any access-check API on top of a graph database works) but the platform's offering saves you that work and gives you consistent audit + tooling.

For RBAC, it's a scope check in your route handler:

if (!req.user.scope.includes('write:documents')) {
return res.status(403).json({ error: 'insufficient_scope' })
}
// proceed

For ReBAC, it's an API call to the platform:

const allowed = await intelliauth.resources.check({
user: req.user.sub,
permission: 'read',
resource: 'doc:123',
})
if (!allowed) {
return res.status(403).json({ error: 'forbidden' })
}

The check is fast (single-digit-millisecond p50, cached aggressively) but it's still a network call. For very hot paths, you can prefetch or use the SDK's batch-check API.

Both RBAC and ReBAC decisions land in the tenant's audit log. For RBAC, the log records "role X granted, role Y revoked." For ReBAC, the log records every tuple write (Alice shared doc:123 with Bob as viewer) and, optionally, every check (Alice asked to read doc:123, allowed). Check-logging is high-volume; sample if you turn it on.