RBAC (Role-Based Access Control)#

Buntok provides middleware for role-based access control.

Info
RBAC middleware must be used AFTER requireAuth because it needs ctx.user.

requireRole#

Requires the user to have at least one of the specified roles. Must be used AFTER requireAuth.

Basic Usage#

typescript
import { requireAuth } from "@buntok/core";
import { requireRole } from "@buntok/core";

const secret = "your-secret-key";

// Single role
app.get("/admin", requireAuth(secret), requireRole("admin"), adminHandler);

        // Multiple roles (OR logic - any one is sufficient)
app.get("/mod", requireAuth(secret), requireRole("admin", "moderator"), modHandler);

Decorator Usage#

Warning
Stage 3: evaluated top-to-bottom, applied bottom-to-top (via unshift). So @Use(requireAuth) must be ABOVE @Use(requireRole) to run first.
typescript
@Get("/admin")
@Use(requireAuth(secret))    // ← executes FIRST
@Use(requireRole("admin"))   // ← executes SECOND
async getAdmin(ctx: Context) {
  const user = ctx.user as { userId: number; role: string };
  return ctx.json({ admin: true });
}

Custom Resolver#

If the role is at a different path in the JWT payload:

typescript
app.get("/admin", requireAuth(secret), requireRole({
  roles: ["admin"],
  resolver: (user) => user.claims.roles,  // custom path
}), adminHandler);

Custom Error Message#

typescript
app.get("/superadmin", requireAuth(secret), requireRole({
  roles: ["superadmin"],
  message: "Only superadmins are allowed to access",
}), superadminHandler);
Info
Auto-detection: requireRole automatically looks for user.role (string) or user.roles (string[]).

requirePermission#

Requires the user to have ALL specified permissions.

Basic Usage#

typescript
// Require ALL permissions
app.delete("/users/:id",
  requireAuth(secret),
  requirePermission("users:delete"),
  deleteUser
);

  // Multiple permissions (all required)
app.post("/posts",
  requireAuth(secret),
  requirePermission("posts:create", "posts:publish"),
  createPost
);

Decorator Usage#

typescript
@Delete("/users/:id")
@Use(requireAuth(secret))
@Use(requirePermission("users:delete"))
async deleteUser(ctx: Context) {
  // ...
}

// Multiple permissions
@Post("/posts")
@Use(requireAuth(secret))
@Use(requirePermission("posts:create", "posts:publish"))
async createPost(ctx: Context) {
  // ...
}

Custom Resolver#

typescript
app.delete("/users/:id",
  requireAuth(secret),
  requirePermission({
    permissions: ["posts:edit", "posts:delete"],
    resolver: (user) => user.scope,  // custom path
  }),
  deleteUser
);

Comparison#

requireRolerequirePermission
LogicOR (any one is sufficient)AND (all required)
Default fielduser.role / user.rolesuser.permissions
ExamplerequireRole("admin", "moderator")requirePermission("posts:create", "posts:publish")

Expected JWT Payload#

typescript
// Default detection for requireRole:
{
  userId: 1,
  role: "admin",           // ← requireRole checks this
  roles: ["admin", "user"] // ← or this
}

// Default detection for requirePermission:
{
  userId: 1,
  permissions: ["users:delete", "posts:create"]  // ← requirePermission checks this
}

Error Responses#

StatusMessage
401Authentication required - ctx.user is missing
403Requires one of: admin, moderator - role does not match
403Missing permissions: users:delete - permission is missing

Next Steps#