Skip to content

Subjects and assignments

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.

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.

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 });

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.

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 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.