Subjects and assignments
Subject
Section titled “Subject”A subject has a required type and an opaque application id, plus optional non-authoritative attributes. It never contains roles.
{ type: "user", id: "user_123", attributes: { mfa: true } }type and id are reserved fact names. Attribute bags cannot override them.
Assignments
Section titled “Assignments”An assignment gives one subject one role at one scope.
{ subject: { type: "user", id: "user_123" }, role: "workspace_editor", scope: { resource: { type: "workspace", id: "ws_456" }, appliesTo: "descendants", },}appliesTo: "self"applies only to the named scope resource.appliesTo: "descendants"applies to that resource and everything below it in the supplied ancestry.
The role’s declared scope type must match the assignment’s scope resource type.
Where assignments come from
Section titled “Where assignments come from”Your application. Rolebook does not store them. Load the current assignments for the subject through an authoritative, tenant-scoped path on every request, then bind:
const authorization = createAuthorizer(policy).bind({ subject, assignments, context });Whole-collection validation
Section titled “Whole-collection validation”Rolebook validates the entire assignment collection before filtering by subject or applicability. One malformed assignment, unknown role, or role/scope mismatch denies the binding, even if that entry belongs to another subject or would not apply to the resource being checked. This is deliberate: a corrupted assignment set is not a safe basis for any decision.
Reusing a validated collection
Section titled “Reusing a validated collection”Every bind() deep-copies and revalidates its assignments. When the same subject is checked against many resources in a row, prepareAssignments() does the copy once:
import { prepareAssignments } from "rolebook";
const prepared = prepareAssignments(assignments) ?? assignments;for (const resource of candidates) { authorizer.bind({ subject, assignments: prepared }).check({ permission, resource });}The result is a frozen copy. A later bind() reuses it instead of copying, but still validates every entry and still enforces size budgets. It returns undefined for a collection that would not bind, so the caller keeps the original and bind() returns its usual closed decision. Preparation is an optimisation and never decides a request.
Context
Section titled “Context”context is an optional bag of request facts, such as a request id or a tenant flag, available to conditions under the context. root. Decide for each value whether a client could have influenced it before you put it there.