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#
| requireRole | requirePermission | |
|---|---|---|
| Logic | OR (any one is sufficient) | AND (all required) |
| Default field | user.role / user.roles | user.permissions |
| Example | requireRole("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#
| Status | Message |
|---|---|
| 401 | Authentication required - ctx.user is missing |
| 403 | Requires one of: admin, moderator - role does not match |
| 403 | Missing permissions: users:delete - permission is missing |
Next Steps#
- Authentication - JWT authentication
- Middleware - Learn about middleware