Skip to content

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.

typescript
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?) ​

ParameterTypeDescription
namestringRole name (must be unique)
permissionsstring[]List of permission strings. Use "*" for all permissions
inheritsstring[]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 ​

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

typescript
// 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); // false

hasAnyPermission(userId, permissions) ​

Check if a user has at least one of the given permissions.

typescript
const canWrite = auth.roles.hasAnyPermission("user-42", [
  "write:posts",
  "write:all",
  "*"
]);

hasAllPermissions(userId, permissions) ​

Check if a user has all of the given permissions.

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

typescript
const isAdmin = auth.roles.hasRole("admin-1", "admin");
console.log(isAdmin); // true

hasAnyRole(userId, roleNames) ​

Check if a user has at least one of the given roles.

typescript
const isPrivileged = auth.roles.hasAnyRole("user-42", [
  "admin",
  "moderator",
]);

Listing and Inspecting ​

typescript
// 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 ​

typescript
// 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 them

Standalone RBAC Functions ​

You can also use the RBAC functions directly without the Auth class.

typescript
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:

ConventionExampleMeaning
action:resourceread:postsRead posts
action:resourcewrite:postsCreate/update posts
action:resourcedelete:postsDelete posts
action:allread:allRead everything
**All permissions (admin only)

Complete Example ​

typescript
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:

typescript
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", "*"); // true

Default Roles ​

Kyrin comes with three built-in roles:

RolePermissionsDescription
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 ​

Released under the MIT License.