Quickstart
This guide walks you through your first authorization check with Toride — from defining a policy to running a permission check — in under 5 minutes.
1. Install Toride
pnpm add toride2. Define a Policy
Create a policy.yaml file in your project root. This policy defines a Project resource with three roles and their permissions, plus a Task resource that inherits roles from its parent project:
# policy.yaml
version: "1"
actors:
User:
attributes:
id: string
email: string
department: string
isSuperAdmin: boolean
global_roles:
superadmin:
actor_type: User
when:
$actor.isSuperAdmin: true
resources:
Project:
roles: [viewer, editor, admin]
permissions: [read, update, delete, create_task]
grants:
viewer: [read]
editor: [read, update, create_task]
admin: [all]
derived_roles:
- role: admin
from_global_role: superadmin
- role: editor
actor_type: User
when:
$actor.department: $resource.department
Task:
roles: [viewer, editor]
permissions: [read, update, delete]
relations:
project: Project
assignee: User
grants:
viewer: [read]
editor: [read, update, delete]
derived_roles:
- role: editor
from_role: editor
on_relation: project
- role: viewer
from_role: viewer
on_relation: project
- role: editor
from_relation: assignee
rules:
- effect: forbid
permissions: [update, delete]
when:
resource.project.status: completedKey things to notice:
- Actors define who can perform actions, along with their attributes
- Global roles are derived from actor attributes (e.g.,
superadminwhenisSuperAdminis true) - Resources define what can be acted upon, with roles, permissions, and grants
- Derived roles propagate roles through relations (e.g., a project
editoris also a taskeditor) - Rules add conditions that can override grants (e.g., forbid edits on completed projects)
3. Create the Engine with Resolvers
Bring the policy and resolvers together to create the engine. Each resolver is a function that returns attributes for a resource instance — including relation references as { type, id } objects. Create src/auth/engine.ts:
import { Toride, loadYaml } from "toride";
// In-memory data — no database needed
const projects: Record<string, any> = {
"proj-1": { status: "active", department: "engineering" },
};
const tasks: Record<string, any> = {
"task-42": {
projectId: "proj-1",
assigneeId: "alice",
status: "open",
},
};
const engine = new Toride({
policy: await loadYaml("./policy.yaml"),
resolvers: {
Task: async (ref) => {
const task = tasks[ref.id];
return {
project: { type: "Project", id: task.projectId },
assignee: { type: "User", id: task.assigneeId },
status: task.status,
};
},
Project: async (ref) => {
const project = projects[ref.id];
return {
status: project.status,
department: project.department,
};
},
},
});
export { engine };Each resolver is a function that returns attributes for a resource instance. Relation fields contain ResourceRef objects ({ type, id }) that the engine follows when traversing relations. Your resolvers can fetch data from any source — here we use plain objects, but you could use a REST API, GraphQL endpoint, file system, or database.
4. Run Your First Permission Check
Now you can check permissions anywhere in your application:
import { engine } from "./auth/engine";
// Define the actor (the user making the request)
const actor = {
type: "User",
id: "alice",
attributes: {
email: "alice@example.com",
department: "engineering",
isSuperAdmin: false,
},
};
// Check if alice can update task 42
const allowed = await engine.can(actor, "update", {
type: "Task",
id: "42",
});
console.log(allowed); // true or falseThe engine will:
- Evaluate all
derived_rolesentries on Task 42 (e.g.,from_roleon project,from_relationassignee) - For relation-based derivation, resolve Task attributes via the
Taskresolver - Follow relations to related resources (Project, User) and check roles there recursively
- Combine all derived roles into a deduplicated set
- Expand grants for the combined role set
- Evaluate any rules (e.g., the
forbidrule for completed projects) - Return the final decision
5. Real-World: Database Resolvers
In production, your resolvers will typically query a database. The resolver interface is the same — only the data source changes:
// When your data source is a database, resolvers look like this:
const engine = new Toride({
policy: await loadYaml("./policy.yaml"),
resolvers: {
Task: async (ref) => {
const task = await db.task.findById(ref.id);
return {
project: { type: "Project", id: task.projectId },
assignee: { type: "User", id: task.assigneeId },
status: task.status,
};
},
Project: async (ref) => {
const project = await db.project.findById(ref.id);
return {
status: project.status,
department: project.department,
};
},
},
});For type-safe resolvers generated from your policy, see Codegen. For query-level filtering with an ORM, see Prisma or Drizzle integration.
6. Explore More Features
Once your basic authorization check is working, explore these features:
Batch Checks
Check multiple permissions in a single call with shared caching:
const results = await engine.canBatch(actor, [
{ action: "read", resource: { type: "Task", id: "1" } },
{ action: "update", resource: { type: "Task", id: "2" } },
{ action: "delete", resource: { type: "Task", id: "3" } },
]);
// results: [true, true, false]List Permitted Actions
Find out which actions an actor can perform on a resource:
const actions = await engine.permittedActions(actor, {
type: "Task",
id: "42",
});
// ["read", "update", "delete"]Debug with Explain
Understand why a permission check returned a particular result:
const decision = await engine.explain(actor, "delete", {
type: "Task",
id: "42",
});
console.log(decision);
// {
// allowed: true,
// resolvedRoles: {
// direct: [],
// derived: [{ role: "editor", via: "from_role editor on_relation project" }]
// },
// grantedPermissions: ["read", "update", "delete"],
// matchedRules: [],
// finalDecision: "ALLOWED: editor grants delete"
// }What's Next?
- Learn about Policy Format to write more advanced policies
- Understand Roles & Relations for complex role derivation
- Set up Partial Evaluation for data filtering
- Integrate with Prisma or Drizzle for query-level filtering