Role-Based Access Control (RBAC)
Kyrin's RBAC system lets you define roles, assign permissions, and check authorization with support for role inheritance.
Overview
RBAC in Kyrin is built around three core concepts:
- Roles — Named sets of permissions (e.g., "admin", "moderator", "user")
- Permissions — String identifiers that represent actions (e.g.,
"read:all","write:posts") - Users — Identities that get assigned one or more roles
Defining Roles
Roles are defined with a name, a list of permissions, and optional parent roles to inherit from.
import { createAuth } from "kyrin";
const auth = createAuth({ jwtSecret: "my-secret" });
// Basic roles
auth.roles.define("admin", ["*"]); // Wildcard = all permissions
auth.roles.define("moderator", ["read:all", "write:posts", "delete:posts"]);
auth.roles.define("user", ["read:own", "update:own"]);
auth.roles.define("guest", ["read:public"]);
// Role inheritance (superadmin inherits all admin permissions)
auth.roles.define("superadmin", ["manage:users"], ["admin"]);define(name, permissions, inherits?)
| Parameter | Type | Description |
|---|---|---|
name | string | Role name (must be unique) |
permissions | string[] | List of permission strings. Use "*" for all permissions |
inherits | string[] | Optional. Parent role names to inherit permissions from |
The "*" wildcard permission grants access to every permission check. This is typically reserved for admin roles.
Assigning Roles to Users
// Assign roles to a user
auth.roles.assign("user-42", ["user"]);
auth.roles.assign("admin-1", ["admin"]);
auth.roles.assign("super-1", ["superadmin"]);
// A user can have multiple roles
auth.roles.assign("mod-1", ["moderator", "user"]);Checking Permissions
hasPermission(userId, permission)
Check if a user has a specific permission.
// Define roles
auth.roles.define("user", ["read:own", "update:own"]);
auth.roles.assign("user-42", ["user"]);
// Check
const canRead = auth.roles.hasPermission("user-42", "read:own");
console.log(canRead); // true
const canDelete = auth.roles.hasPermission("user-42", "delete:posts");
console.log(canDelete); // falsehasAnyPermission(userId, permissions)
Check if a user has at least one of the given permissions.
const canWrite = auth.roles.hasAnyPermission("user-42", [
"write:posts",
"write:all",
"*"
]);hasAllPermissions(userId, permissions)
Check if a user has all of the given permissions.
const canManage = auth.roles.hasAllPermissions("admin-1", [
"read:all",
"write:posts",
"delete:posts",
]);Checking Roles
hasRole(userId, roleName)
Check if a user has a specific role.
const isAdmin = auth.roles.hasRole("admin-1", "admin");
console.log(isAdmin); // truehasAnyRole(userId, roleNames)
Check if a user has at least one of the given roles.
const isPrivileged = auth.roles.hasAnyRole("user-42", [
"admin",
"moderator",
]);Listing and Inspecting
// Get all defined roles
const allRoles = auth.roles.list();
// Returns: [{ name: "admin", permissions: ["*"] }, ...]
// Get a specific role definition
const adminRole = auth.roles.get("admin");
// Returns: { name: "admin", permissions: ["*"], inherits?: string[] }
// Get all roles assigned to a user
const userRoles = auth.roles.getNames("user-42");
// Returns: ["user"]
// Get all permissions a user has (including inherited)
const permissions = auth.roles.getPermissions("super-1");
// Returns: ["manage:users", "*"] (own + inherited from admin)Revoking and Clearing
// Remove all roles from a user
auth.roles.revoke("user-42");
console.log(auth.roles.getNames("user-42")); // []
// Clear all role definitions and assignments
auth.roles.clear();
// After clear(), no roles exist — you must redefine themStandalone RBAC Functions
You can also use the RBAC functions directly without the Auth class.
import {
defineRole,
getRole,
getAllRoles,
setUserRoles,
getUserRoleNames,
getUserPermissions,
hasPermission,
hasAnyPermission,
hasAllPermissions,
hasRole,
hasAnyRole,
removeUserRoles,
} from "kyrin";
defineRole("editor", ["read:all", "write:posts", "write:pages"]);
setUserRoles("user-42", ["editor"]);
if (hasPermission("user-42", "write:pages")) {
console.log("User can edit pages");
}Permission Naming Convention
While you can use any string for permissions, we recommend a action:resource convention:
| Convention | Example | Meaning |
|---|---|---|
action:resource | read:posts | Read posts |
action:resource | write:posts | Create/update posts |
action:resource | delete:posts | Delete posts |
action:all | read:all | Read everything |
* | * | All permissions (admin only) |
Complete Example
import { createAuth } from "kyrin";
import { createApp } from "kyrin";
const auth = createAuth({ jwtSecret: process.env.JWT_SECRET! });
const app = createApp();
// --- Define roles ---
auth.roles.define("admin", ["*"]);
auth.roles.define("moderator", ["read:all", "write:posts", "delete:posts"]);
auth.roles.define("user", ["read:own", "update:own"]);
// --- Auth middleware ---
app.use(async (c, next) => {
const token = c.req.headers.get("Authorization")?.slice(7);
if (!token) return c.json({ error: "Unauthorized" }, 401);
const payload = await auth.verifyAccessToken(token);
if (!payload) return c.json({ error: "Invalid token" }, 401);
c.store.userId = payload.sub;
c.store.role = payload.role;
await next();
});
// --- Authorization middleware factory ---
function requirePermission(permission: string) {
return async (c: any, next: () => Promise<void>) => {
if (!auth.roles.hasPermission(c.store.userId, permission)) {
return c.json({ error: "Forbidden" }, 403);
}
await next();
};
}
// --- Routes ---
app.get("/posts", async (c) => {
// Any authenticated user can read their own posts
if (!auth.roles.hasPermission(c.store.userId, "read:own")) {
return c.json({ error: "Forbidden" }, 403);
}
return c.json(await getPosts(c.store.userId));
});
app.post("/posts", requirePermission("write:posts"), async (c) => {
const data = await c.body();
return c.json(await createPost(data));
});
app.delete("/posts/:id", requirePermission("delete:posts"), async (c) => {
await deletePost(c.param("id"));
return c.json({ success: true });
});
// Admin-only route (wildcard permission)
app.get("/admin/logs", requirePermission("*"), async (c) => {
return c.json(await getLogs());
});Role Inheritance
Roles can inherit permissions from other roles. This creates a hierarchy:
auth.roles.define("user", ["read:own", "update:own"]);
auth.roles.define("moderator", ["read:all", "write:posts", "delete:posts"], ["user"]);
auth.roles.define("admin", ["*"], ["moderator"]);
// admin inherits all moderator and user permissions
auth.roles.assign("admin-1", ["admin"]);
// This is true because admin inherits from moderator which inherits from user
auth.roles.hasPermission("admin-1", "read:own"); // true
auth.roles.hasPermission("admin-1", "delete:posts"); // true
auth.roles.hasPermission("admin-1", "*"); // trueDefault Roles
Kyrin comes with three built-in roles:
| Role | Permissions | Description |
|---|---|---|
admin | ["*"] | Full access to everything |
user | ["read:own", "update:own"] | Standard user access |
guest | ["read:public"] | Read-only public access |
You can override or remove these by calling clear() and redefining them.
Next Steps
- Authentication — JWT tokens and the Auth class
- Session Management — User session handling
- OAuth2 — Social login integration