Back to list
cameronapak

bknd-setup-auth

by cameronapak

A no-build, un-bloated stack built upon Web Standards that feels freeing to use and can be deployed anywhere.

23🍴 2📅 Jan 21, 2026

SKILL.md


Setup Authentication

Initialize and configure the Bknd authentication system with strategies, JWT, cookies, and roles.

Prerequisites

  • Bknd project initialized
  • Code-first configuration (auth config is code-only)
  • For OAuth: provider credentials (client ID, client secret)

When to Use UI Mode

  • Viewing current auth configuration
  • Toggling strategies on/off
  • Testing auth endpoints via admin panel

UI steps: Admin Panel > Auth > Settings

Note: Full auth configuration requires code mode. UI only shows/toggles existing settings.

When to Use Code Mode

  • Initial authentication setup
  • Configuring JWT secrets and expiry
  • Setting up password hashing
  • Defining roles and permissions
  • Production security hardening

Code Approach

Step 1: Enable Auth with Minimal Config

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:

  • Password strategy (default)
  • Auto-created users entity
  • JWT-based sessions
  • /api/auth/* endpoints

Step 2: Configure JWT Settings

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

OptionTypeDefaultDescription
secretstring""Signing secret (256-bit minimum for production)
algstring"HS256"Algorithm: HS256, HS384, HS512
expiresnumber-Token expiry in seconds
issuerstring-Token issuer claim (iss)
fieldsstring[]["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:

OptionTypeDefaultDescription
securebooleantrueHTTPS-only flag
httpOnlybooleantrueBlock JS access
sameSitestring"lax"CSRF protection
expiresnumber604800Expiry in seconds
renewbooleantrueAuto-extend expiry
pathSuccessstring"/"Post-login redirect
pathLoggedOutstring"/"Post-logout redirect

Step 4: Configure Password Strategy

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:

OptionSecurityPerformanceUse Case
plainNoneFastestDevelopment only, never production
sha256GoodFastDefault, suitable for most cases
bcryptBestSlowerRecommended for production

Step 5: Define Roles

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
  },
}

Step 6: Configure 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
  },
}

Full Production Example

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: "admin@example.com" } },
      });

      if (!adminExists) {
        await ctx.app.module.auth.createUser({
          email: "admin@example.com",
          password: process.env.ADMIN_PASSWORD || "changeme123",
          role: "admin",
        });
        console.log("Admin user created");
      }
    },
  },
};

serve(config);

Auth Endpoints

After setup, these endpoints are available:

MethodPathDescription
POST/api/auth/password/loginLogin with email/password
POST/api/auth/password/registerRegister new user
GET/api/auth/meGet current user
POST/api/auth/logoutLog out (clear cookie)
GET/api/auth/strategiesList enabled strategies

Environment Variables

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

Development vs Production

SettingDevelopmentProduction
jwt.secretCan use placeholderRequired, strong secret
cookie.securefalsetrue (HTTPS only)
strategies.password.config.hashingsha256bcrypt
allow_registertrueConsider 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",
        },
      },
    },
  },
}

Common Pitfalls

Missing JWT Secret in Production

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
    },
  },
}

Role Not Found

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
  },
}

Registration Disabled

Problem: Registration not allowed error

Fix: Enable registration:

{
  auth: {
    allow_register: true,  // Default is true, but check if explicitly disabled
  },
}

Weak Password Hashing

Problem: Using plain or sha256 in production

Fix: Use bcrypt for production:

{
  auth: {
    strategies: {
      password: {
        config: {
          hashing: "bcrypt",
          rounds: 4,  // Balance security and performance
        },
      },
    },
  },
}

Verification

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": "test@example.com", "password": "password123"}'

3. Login:

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

4. Check current user (with token):

curl http://localhost:7654/api/auth/me \
  -H "Authorization: Bearer <token-from-login>"

Security Checklist

Before deploying to production:

  • Set strong jwt.secret (256-bit minimum)
  • Use hashing: "bcrypt" for password strategy
  • Set cookie.secure: true (HTTPS only)
  • Set cookie.httpOnly: true (default)
  • Set cookie.sameSite: "lax" or "strict"
  • Configure jwt.expires (don't leave unlimited)
  • Review allow_register setting
  • Create admin user via seed (not via public registration)
  • Store secrets in environment variables

DOs and DON'Ts

DO:

  • Use environment variables for secrets
  • Use bcrypt hashing in production
  • Set JWT expiry times
  • Define roles before assigning them
  • Test auth flow after configuration changes

DON'T:

  • Hardcode JWT secrets in code
  • Use plain hashing in production
  • Skip setting cookie.secure in production
  • Leave registration open if not needed
  • Forget to create initial admin user
  • bknd-create-user - Create user accounts programmatically
  • bknd-login-flow - Implement login/logout functionality
  • bknd-registration - Set up user registration flows
  • bknd-oauth-setup - Configure OAuth providers (Google, GitHub)
  • bknd-create-role - Define roles for authorization
  • bknd-session-handling - Manage user sessions

Score

Total Score

75/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon