home / skills / cameronapak / bknd-skills / bknd-create-role
This skill helps you define and manage Bknd authorization roles, including permissions, defaults, and hierarchies, for secure, scalable access control.
npx playbooks add skill cameronapak/bknd-skills --skill bknd-create-roleReview the files below or copy the command above to add this skill to your agents.
---
name: bknd-create-role
description: Use when defining a new role in Bknd authorization system. Covers role properties (implicit_allow, is_default, permissions), permission assignment, role hierarchies, and common role patterns (admin, editor, viewer, anonymous).
---
# Create Role
Define a new role in Bknd's authorization system to control user access.
## Prerequisites
- Bknd project initialized with code-first configuration
- Auth enabled (`auth: { enabled: true }`)
- Guard enabled for authorization (`guard: { enabled: true }`)
## When to Use UI Mode
- Viewing existing roles
- Quick toggle of role settings
**UI steps:** Admin Panel > Auth > Roles
**Note:** Role creation requires code mode. UI only shows existing roles.
## When to Use Code Mode
- Creating new roles
- Setting role permissions
- Configuring default roles
- Setting up role hierarchies
## Code Approach
### Step 1: Enable Guard
Roles require the guard system to be enabled:
```typescript
import { serve } from "bknd/adapter/bun";
import { em, entity, text } from "bknd";
const schema = em({
posts: entity("posts", { title: text().required() }),
});
serve({
connection: { url: "file:data.db" },
config: {
data: schema.toJSON(),
auth: {
enabled: true,
guard: { enabled: true }, // Required for roles
roles: {
// Roles defined here
},
},
},
});
```
### Step 2: Define a Basic Role
Create a role with explicit permissions:
```typescript
{
auth: {
enabled: true,
guard: { enabled: true },
roles: {
viewer: {
implicit_allow: false, // Deny by default
permissions: [
"data.entity.read", // Grant read access only
],
},
},
},
}
```
### Role Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `implicit_allow` | boolean | `false` | Allow all unless denied |
| `is_default` | boolean | `false` | Use when user has no role |
| `permissions` | array | `[]` | Permissions granted to role |
### Step 3: Create Admin Role (Full Access)
Grant full access with `implicit_allow`:
```typescript
{
roles: {
admin: {
implicit_allow: true, // Can do everything
},
},
}
```
**Warning:** `implicit_allow: true` grants ALL permissions. Use only for admin roles.
### Step 4: Create Editor Role (Partial Access)
Grant specific CRUD permissions:
```typescript
{
roles: {
editor: {
implicit_allow: false,
permissions: [
"data.entity.read",
"data.entity.create",
"data.entity.update",
// No delete permission
],
},
},
}
```
### Step 5: Create Default Role
Set a role for users without assigned role:
```typescript
{
roles: {
anonymous: {
is_default: true, // Applied when no role
implicit_allow: false,
permissions: [
"data.entity.read", // Read-only access
],
},
},
}
```
**Note:** Only ONE role can have `is_default: true`.
### Step 6: Set Registration Role
Assign role to newly registered users:
```typescript
{
auth: {
enabled: true,
default_role_register: "user", // Role for new registrations
roles: {
user: {
implicit_allow: false,
permissions: ["data.entity.read"],
},
},
},
}
```
## Available Permissions
| Permission | Description |
|------------|-------------|
| `data.entity.read` | Read any entity records |
| `data.entity.create` | Create records in any entity |
| `data.entity.update` | Update records in any entity |
| `data.entity.delete` | Delete records from any entity |
| `data.database.sync` | Sync database schema |
| `data.raw.query` | Execute raw SELECT queries |
| `data.raw.mutate` | Execute raw INSERT/UPDATE/DELETE |
## Common Role Patterns
### Multi-Tier Access System
```typescript
{
auth: {
enabled: true,
guard: { enabled: true },
default_role_register: "user",
roles: {
// Full access
admin: {
implicit_allow: true,
},
// Content management
editor: {
implicit_allow: false,
permissions: [
"data.entity.read",
"data.entity.create",
"data.entity.update",
"data.entity.delete",
],
},
// Create and read
contributor: {
implicit_allow: false,
permissions: [
"data.entity.read",
"data.entity.create",
],
},
// Authenticated read-only
user: {
implicit_allow: false,
permissions: [
"data.entity.read",
],
},
// Unauthenticated/guest
anonymous: {
is_default: true,
implicit_allow: false,
permissions: [
"data.entity.read",
],
},
},
},
}
```
### Closed System (No Public Access)
```typescript
{
auth: {
enabled: true,
guard: { enabled: true },
allow_register: false, // Disable self-registration
roles: {
admin: {
implicit_allow: true,
},
member: {
implicit_allow: false,
permissions: [
"data.entity.read",
"data.entity.create",
"data.entity.update",
],
},
// No default role - unauthenticated users get NO access
},
},
}
```
### API Consumer Role
```typescript
{
roles: {
api_client: {
implicit_allow: false,
permissions: [
"data.entity.read",
"data.entity.create",
// No update/delete - API clients create data only
],
},
},
}
```
## Permission Effects
Use extended format for allow/deny effects:
```typescript
{
roles: {
moderator: {
implicit_allow: false,
permissions: [
{ permission: "data.entity.read", effect: "allow" },
{ permission: "data.entity.update", effect: "allow" },
{ permission: "data.entity.delete", effect: "deny" }, // Explicit deny
],
},
},
}
```
## Role Assignment
### Assign During User Creation (Seed)
```typescript
{
options: {
seed: async (ctx) => {
await ctx.app.module.auth.createUser({
email: "[email protected]",
password: "secure-password",
role: "admin", // Assign admin role
});
},
},
}
```
### Assign During Registration
```typescript
{
auth: {
default_role_register: "user", // All registrations get "user" role
},
}
```
### Update User Role (API)
```typescript
const api = getApi(app);
// Update user's role
await api.data.updateOne("users", userId, {
role: "editor",
});
```
## Verification
Test role permissions:
**1. Create user with role:**
```bash
curl -X POST http://localhost:7654/api/auth/password/register \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "password123"}'
```
**2. Login and get token:**
```bash
curl -X POST http://localhost:7654/api/auth/password/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "password123"}'
```
**3. Test permission (should succeed for read):**
```bash
curl http://localhost:7654/api/data/posts \
-H "Authorization: Bearer <token>"
```
**4. Test denied permission (should fail for delete if not allowed):**
```bash
curl -X DELETE http://localhost:7654/api/data/posts/1 \
-H "Authorization: Bearer <token>"
# Returns 403 if delete not in permissions
```
## Common Pitfalls
### No Default Role
**Problem:** `User has no role` error for unauthenticated users
**Fix:** Set a default role:
```typescript
{
roles: {
anonymous: {
is_default: true,
permissions: ["data.entity.read"],
},
},
}
```
### Multiple Default Roles
**Problem:** Unpredictable behavior with multiple `is_default: true`
**Fix:** Only ONE role should be default:
```typescript
{
roles: {
user: { is_default: true }, // Only one!
guest: { /* no is_default */ },
},
}
```
### Role Not Found
**Problem:** `Role "admin" not found` when assigning
**Fix:** Define role before referencing:
```typescript
{
auth: {
roles: {
admin: { implicit_allow: true }, // Define first
},
default_role_register: "admin", // Then reference
},
}
```
### Guard Not Enabled
**Problem:** Roles defined but permissions not enforced
**Fix:** Enable the guard:
```typescript
{
auth: {
enabled: true,
guard: { enabled: true }, // Required!
roles: { /* ... */ },
},
}
```
### Implicit Allow Overuse
**Problem:** Using `implicit_allow: true` on non-admin roles
**Fix:** Be explicit about permissions:
```typescript
// WRONG - too permissive
{
roles: {
editor: { implicit_allow: true },
},
}
// CORRECT - explicit permissions
{
roles: {
editor: {
implicit_allow: false,
permissions: [
"data.entity.read",
"data.entity.create",
"data.entity.update",
],
},
},
}
```
## DOs and DON'Ts
**DO:**
- Enable guard when using roles
- Use `implicit_allow: false` for non-admin roles
- Set one default role for unauthenticated access
- Define roles before referencing them
- Test each role's permissions after creation
**DON'T:**
- Use `implicit_allow: true` for non-admin roles
- Set multiple roles as default
- Forget to enable guard
- Grant `data.raw.*` permissions to untrusted roles
- Assume roles work without guard enabled
## Related Skills
- **bknd-setup-auth** - Initialize authentication system
- **bknd-assign-permissions** - Configure detailed permissions with policies
- **bknd-row-level-security** - Implement row-level access control
- **bknd-protect-endpoint** - Secure specific endpoints
- **bknd-public-vs-auth** - Configure public vs authenticated access
This skill helps you define and manage roles in the Bknd authorization system. It covers role properties (implicit_allow, is_default, permissions), permission assignment formats (allow/deny), role hierarchies, and common role patterns like admin, editor, viewer, and anonymous. Use it to design predictable access models and seed roles for registration or administration.
Roles are declared in code mode inside the auth.guard configuration. Each role can set implicit_allow (global allow unless denied), is_default (applied to unauthenticated users), and an array of permissions or permission objects with effect: allow/deny. The guard must be enabled for roles to be enforced. You assign roles at registration, user creation seeds, or by updating the user record.
What does implicit_allow do?
implicit_allow: true grants all permissions by default unless an explicit deny is present. Use it only for full-admin roles.
How do I give a user a role at signup?
Set auth.default_role_register to the role name to auto-assign it on registration, or create users in a seed script with the role field.
Why are my role permissions not enforced?
Most likely the guard is not enabled. Ensure auth.enabled and auth.guard.enabled are both true.