.agents/skills/bknd-setup-auth/SKILL.md
Use when initializing or configuring the Bknd authentication system. Covers enabling auth, configuring password strategy, setting up JWT and cookie options, defining roles, and production security settings.
npx skillsauth add cameronapak/cultivate-fellowship bknd-setup-authInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Initialize and configure the Bknd authentication system with strategies, JWT, cookies, and roles.
UI steps: Admin Panel > Auth > Settings
Note: Full auth configuration requires code mode. UI only shows/toggles existing settings.
Start with basic password authentication:
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,
},
},
});
This enables:
users entity/api/auth/* endpointsJWT tokens authenticate API requests. Configure for security:
{
auth: {
enabled: true,
jwt: {
secret: process.env.JWT_SECRET, // Required in production
alg: "HS256", // Algorithm: HS256, HS384, HS512
expires: 604800, // Expiry in seconds (7 days)
issuer: "my-app", // Optional issuer claim
fields: ["id", "email", "role"], // Fields included in token
},
},
}
JWT options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| secret | string | "" | Signing secret (256-bit minimum for production) |
| alg | string | "HS256" | Algorithm: HS256, HS384, HS512 |
| expires | number | - | Token expiry in seconds |
| issuer | string | - | Token issuer claim (iss) |
| fields | string[] | ["id", "email", "role"] | User fields in payload |
Auth cookies store JWT tokens for browser sessions:
{
auth: {
enabled: true,
jwt: { secret: process.env.JWT_SECRET },
cookie: {
secure: true, // HTTPS only (set false for local dev)
httpOnly: true, // Block JavaScript access
sameSite: "lax", // CSRF protection: "strict" | "lax" | "none"
expires: 604800, // Cookie expiry in seconds (7 days)
path: "/", // Cookie path scope
renew: true, // Auto-extend on requests
pathSuccess: "/", // Redirect after login
pathLoggedOut: "/", // Redirect after logout
},
},
}
Cookie options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| secure | boolean | true | HTTPS-only flag |
| httpOnly | boolean | true | Block JS access |
| sameSite | string | "lax" | CSRF protection |
| expires | number | 604800 | Expiry in seconds |
| renew | boolean | true | Auto-extend expiry |
| pathSuccess | string | "/" | Post-login redirect |
| pathLoggedOut | string | "/" | Post-logout redirect |
Set up password hashing and requirements:
{
auth: {
enabled: true,
jwt: { secret: process.env.JWT_SECRET },
strategies: {
password: {
type: "password",
enabled: true,
config: {
hashing: "bcrypt", // "plain" | "sha256" | "bcrypt"
rounds: 4, // bcrypt rounds (1-10)
minLength: 8, // Minimum password length
},
},
},
},
}
Hashing options:
| Option | Security | Performance | Use Case |
|--------|----------|-------------|----------|
| plain | None | Fastest | Development only, never production |
| sha256 | Good | Fast | Default, suitable for most cases |
| bcrypt | Best | Slower | Recommended for production |
Configure roles for authorization:
{
auth: {
enabled: true,
jwt: { secret: process.env.JWT_SECRET },
roles: {
admin: {
implicit_allow: true, // Can do everything
},
editor: {
implicit_allow: false,
permissions: [
{ permission: "data.posts.read", effect: "allow" },
{ permission: "data.posts.create", effect: "allow" },
{ permission: "data.posts.update", effect: "allow" },
],
},
user: {
implicit_allow: false,
is_default: true, // Default role for new registrations
permissions: [
{ permission: "data.posts.read", effect: "allow" },
],
},
},
default_role_register: "user", // Role assigned on registration
},
}
Control user self-registration:
{
auth: {
enabled: true,
allow_register: true, // Enable/disable registration
default_role_register: "user", // Role for new users
entity_name: "users", // User entity name (default: "users")
basepath: "/api/auth", // Auth API base path
},
}
Complete auth setup with security best practices:
import { serve, type BunBkndConfig } from "bknd/adapter/bun";
import { em, entity, text, date } from "bknd";
const schema = em({
users: entity("users", {
email: text().required().unique(),
name: text(),
avatar: text(),
created_at: date({ default_value: "now" }),
}),
posts: entity("posts", {
title: text().required(),
content: text(),
}),
});
type Database = (typeof schema)["DB"];
declare module "bknd" {
interface DB extends Database {}
}
const config: BunBkndConfig = {
connection: { url: process.env.DB_URL || "file:data.db" },
config: {
data: schema.toJSON(),
auth: {
enabled: true,
basepath: "/api/auth",
entity_name: "users",
allow_register: true,
default_role_register: "user",
// JWT configuration
jwt: {
secret: process.env.JWT_SECRET!,
alg: "HS256",
expires: 604800, // 7 days
issuer: "my-app",
fields: ["id", "email", "role"],
},
// Cookie configuration
cookie: {
secure: process.env.NODE_ENV === "production",
httpOnly: true,
sameSite: "lax",
expires: 604800,
renew: true,
pathSuccess: "/dashboard",
pathLoggedOut: "/login",
},
// Password strategy
strategies: {
password: {
type: "password",
enabled: true,
config: {
hashing: "bcrypt",
rounds: 4,
minLength: 8,
},
},
},
// Roles
roles: {
admin: {
implicit_allow: true,
},
editor: {
implicit_allow: false,
permissions: [
{ permission: "data.posts.read", effect: "allow" },
{ permission: "data.posts.create", effect: "allow" },
{ permission: "data.posts.update", effect: "allow" },
{ permission: "data.posts.delete", effect: "allow" },
],
},
user: {
implicit_allow: false,
is_default: true,
permissions: [
{ permission: "data.posts.read", effect: "allow" },
],
},
},
},
},
options: {
seed: async (ctx) => {
// Create initial admin on first run
const adminExists = await ctx.em.repo("users").findOne({
where: { email: { $eq: "[email protected]" } },
});
if (!adminExists) {
await ctx.app.module.auth.createUser({
email: "[email protected]",
password: process.env.ADMIN_PASSWORD || "changeme123",
role: "admin",
});
console.log("Admin user created");
}
},
},
};
serve(config);
After setup, these endpoints are available:
| Method | Path | Description |
|--------|------|-------------|
| POST | /api/auth/password/login | Login with email/password |
| POST | /api/auth/password/register | Register new user |
| GET | /api/auth/me | Get current user |
| POST | /api/auth/logout | Log out (clear cookie) |
| GET | /api/auth/strategies | List enabled strategies |
Recommended env vars for auth:
# .env
JWT_SECRET=your-256-bit-secret-minimum-32-characters-long
ADMIN_PASSWORD=secure-initial-admin-password
Generate a secure secret:
# Generate 64-character random string
openssl rand -hex 32
| Setting | Development | Production |
|---------|-------------|------------|
| jwt.secret | Can use placeholder | Required, strong secret |
| cookie.secure | false | true (HTTPS only) |
| strategies.password.config.hashing | sha256 | bcrypt |
| allow_register | true | Consider false for closed systems |
Dev config shortcut:
const isDev = process.env.NODE_ENV !== "production";
{
auth: {
enabled: true,
jwt: {
secret: isDev ? "dev-secret-not-for-production" : process.env.JWT_SECRET!,
expires: isDev ? 86400 * 30 : 604800, // 30 days dev, 7 days prod
},
cookie: {
secure: !isDev,
},
strategies: {
password: {
type: "password",
config: {
hashing: isDev ? "sha256" : "bcrypt",
},
},
},
},
}
Problem: Cannot sign JWT without secret error
Fix: Set JWT secret via environment variable:
{
auth: {
jwt: {
secret: process.env.JWT_SECRET, // Never hardcode in production
},
},
}
Problem: Auth cookie not set in browser
Fix: Set secure: false for local development:
{
auth: {
cookie: {
secure: process.env.NODE_ENV === "production", // false for localhost
},
},
}
Problem: Role "admin" not found when creating users
Fix: Define roles before referencing them:
{
auth: {
roles: {
admin: { implicit_allow: true }, // Define first
user: { implicit_allow: false },
},
default_role_register: "user", // Now can reference
},
}
Problem: Registration not allowed error
Fix: Enable registration:
{
auth: {
allow_register: true, // Default is true, but check if explicitly disabled
},
}
Problem: Using plain or sha256 in production
Fix: Use bcrypt for production:
{
auth: {
strategies: {
password: {
config: {
hashing: "bcrypt",
rounds: 4, // Balance security and performance
},
},
},
},
}
After setup, verify auth works:
1. Check enabled strategies:
curl http://localhost:7654/api/auth/strategies
2. Register a test user:
curl -X POST http://localhost:7654/api/auth/password/register \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "password123"}'
3. Login:
curl -X POST http://localhost:7654/api/auth/password/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "password123"}'
4. Check current user (with token):
curl http://localhost:7654/api/auth/me \
-H "Authorization: Bearer <token-from-login>"
Before deploying to production:
jwt.secret (256-bit minimum)hashing: "bcrypt" for password strategycookie.secure: true (HTTPS only)cookie.httpOnly: true (default)cookie.sameSite: "lax" or "strict"jwt.expires (don't leave unlimited)allow_register settingDO:
DON'T:
plain hashing in productioncookie.secure in productiondevelopment
Use btca (Better Context App) to efficiently query and learn from the bknd backend framework. Use when working with bknd for (1) Understanding data module and schema definitions, (2) Implementing authentication and authorization, (3) Setting up media file handling, (4) Configuring adapters (Node, Cloudflare, etc.), (5) Learning from bknd source code and examples, (6) Debugging bknd-specific issues
development
Use when configuring webhook integrations in Bknd. Covers receiving incoming webhooks via HTTP triggers, sending outgoing webhooks with FetchTask, event-triggered webhooks on data changes, signature verification, retry patterns, and async processing.
development
Use when encountering Bknd errors, getting error messages, something not working, or needing quick fixes. Covers error code reference, quick solutions, and common mistake patterns.
tools
Use when writing tests for Bknd applications, setting up test infrastructure, creating unit/integration tests, or testing API endpoints. Covers in-memory database setup, test helpers, mocking, and test patterns.