RBAC & permissions
OrthID authorises every action with role-based access control. Roles bundle permissions, sessions carry scopes, and least privilege is the default.
Authentication answers “who is this?”. Authorisation answers “what may they do?”. OrthID answers the second with RBAC built from three primitives: permissions, roles and scopes. They apply uniformly to all three actors, so a human, an organisation and an agent are all checked the same way.
Permissions, roles and scopes
- Permission - the smallest unit of access, written
resource:action, for examplerecords:readormembers:invite. - Role - a named bundle of permissions, such as
clinicianororg:admin. You assign roles, not individual permissions. - Scope - the set of permissions resolved onto a live session or token. The scope is what your code actually checks at runtime.
Least privilege
OrthID grants nothing by default. A new actor has no permissions until a role is assigned, and an agent has no reach until a scope is issued. Always grant the narrowest role that lets the actor do its job, and prefer many small roles over one broad one. This keeps the blast radius of any single credential small.
Defining a role
Define roles as a bundle of permissions. Roles can be built in (org:admin, org:member) or custom to your product:
import { orthid } from "@orthid/sdk";
await orthid.roles.create({
organization: "org_2bT7uX",
key: "clinician",
name: "Clinician",
permissions: [
"records:read",
"records:write",
"summaries:write",
],
});Checking a permission
At runtime, verify the session and check the resolved scope. The scope already accounts for the actor’s roles in the active organisation, so you check one flat list:
import { orthid } from "@orthid/sdk";
const { session } = await orthid.sessions.verify(token);
if (!session.scope.includes("records:write")) {
return new Response("Forbidden", { status: 403 });
}
// proceed - the actor is allowed to write recordsOrg-scoped roles
Roles are scoped to an organisation, not global. The same human can be org:admin in one organisation and org:member in another. Because a session has one active organisation at a time, the resolved scope reflects only that organisation’s roles. Switching organisations (via <OrgSwitcher/>) re-resolves the scope.
How scopes apply to agents
Agents are RBAC citizens too, with one extra rule: an agent’s scope must be a subset of the human it acts on behalf of. When you issue an agent credential, OrthID intersects the scope you request with what the principal human is permitted in that organisation. The agent can never out-reach its human.
import { orthid } from "@orthid/sdk";
// Requested scope is intersected with user_3kP9aZ's permissions.
const agent = await orthid.agents.issue({
onBehalfOf: "user_3kP9aZ",
scope: ["records:read"], // narrower than the human's full role
ttl: "10m",
region: "au-syd-1",
});Next steps
- The three actors - how roles attach to humans, organisations and agents.
- Scope an agent - apply these scopes in practice.