Agent Skills: Create Role

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

UncategorizedID: cameronapak/bknd-skills/bknd-create-role

Install this agent skill to your local

pnpm dlx add-skill https://github.com/cameronapak/bknd-skills/tree/HEAD/skills/bknd-create-role

Skill Files

Browse the full folder contents for bknd-create-role.

Download Skill

Loading file tree…

skills/bknd-create-role/SKILL.md

Skill Metadata

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:

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:

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

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

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

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

{
  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

{
  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)

{
  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

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

{
  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)

{
  options: {
    seed: async (ctx) => {
      await ctx.app.module.auth.createUser({
        email: "admin@example.com",
        password: "secure-password",
        role: "admin",  // Assign admin role
      });
    },
  },
}

Assign During Registration

{
  auth: {
    default_role_register: "user",  // All registrations get "user" role
  },
}

Update User Role (API)

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:

curl -X POST http://localhost:7654/api/auth/password/register \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "password": "password123"}'

2. Login and get token:

curl -X POST http://localhost:7654/api/auth/password/login \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "password": "password123"}'

3. Test permission (should succeed for read):

curl http://localhost:7654/api/data/posts \
  -H "Authorization: Bearer <token>"

4. Test denied permission (should fail for delete if not allowed):

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:

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

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

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

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

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