Claude
Skills
Sign in
Back

bknd-public-vs-auth

Included with Lifetime
$97 forever

Use when configuring public vs authenticated access in Bknd. Covers anonymous role setup, unauthenticated data access, public/private entity patterns, mixed access modes, and protecting sensitive entities while exposing public ones.

General

What this skill does


# Public vs Authenticated Access

Configure which data and endpoints are publicly accessible vs require authentication.

## Prerequisites

- Bknd project with code-first configuration
- Auth enabled (`auth: { enabled: true }`)
- Guard enabled (`guard: { enabled: true }`)
- Basic understanding of roles (see **bknd-create-role**)

## When to Use UI Mode

- Viewing current role configurations
- Inspecting permission assignments

**UI steps:** Admin Panel > Auth > Roles

**Note:** Access configuration requires code mode.

## When to Use Code Mode

- Setting up anonymous/default role for public access
- Configuring entity-specific access rules
- Creating mixed public/private data patterns
- Building closed (auth-required) systems

## Core Concept: Default Role

Bknd uses the **default role** to determine what unauthenticated users can access:

```
User makes request → Has token? → Yes → Use user's role
                              → No  → Use default role (is_default: true)
                                    → No default? → ACCESS DENIED
```

## Code Approach

### Step 1: Fully Public (Read-Only)

Allow unauthenticated users to read all data:

```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 },
      roles: {
        // Public role - anyone can read
        anonymous: {
          is_default: true,
          implicit_allow: false,
          permissions: ["data.entity.read"],
        },
        // Authenticated users can create/update
        user: {
          implicit_allow: false,
          permissions: [
            "data.entity.read",
            "data.entity.create",
            "data.entity.update",
          ],
        },
      },
    },
  },
});
```

**Result:**
- `GET /api/data/posts` - Works without auth
- `POST /api/data/posts` - Requires auth
- `PATCH /api/data/posts/1` - Requires auth

### Step 2: Fully Private (Auth Required)

Require authentication for all access:

```typescript
{
  auth: {
    enabled: true,
    guard: { enabled: true },
    allow_register: true,
    default_role_register: "user",
    roles: {
      admin: { implicit_allow: true },
      user: {
        implicit_allow: false,
        permissions: [
          "data.entity.read",
          "data.entity.create",
          "data.entity.update",
        ],
      },
      // NO default role - unauthenticated users get nothing
    },
  },
}
```

**Result:** All `/api/data/*` endpoints return 403 without authentication.

### Step 3: Entity-Specific Public Access

Make some entities public, others private:

```typescript
{
  auth: {
    enabled: true,
    guard: { enabled: true },
    roles: {
      anonymous: {
        is_default: true,
        implicit_allow: false,
        permissions: [
          // Only posts are public
          {
            permission: "data.entity.read",
            effect: "allow",
            policies: [{
              condition: { entity: "posts" },
              effect: "allow",
            }],
          },
        ],
      },
      user: {
        implicit_allow: false,
        permissions: [
          "data.entity.read",   // Read all entities
          "data.entity.create",
          "data.entity.update",
        ],
      },
    },
  },
}
```

**Result:**
- `GET /api/data/posts` - Public
- `GET /api/data/users` - Requires auth
- `GET /api/data/comments` - Requires auth

### Step 4: Multiple Public Entities

Expose several entities publicly:

```typescript
{
  roles: {
    anonymous: {
      is_default: true,
      implicit_allow: false,
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            condition: { entity: { $in: ["posts", "categories", "tags"] } },
            effect: "allow",
          }],
        },
      ],
    },
  },
}
```

### Step 5: Public Records with Filter

Make only published/public records accessible:

```typescript
{
  roles: {
    anonymous: {
      is_default: true,
      implicit_allow: false,
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [
            // Posts: only published
            {
              condition: { entity: "posts" },
              effect: "filter",
              filter: { status: "published" },
            },
            // Products: only visible
            {
              condition: { entity: "products" },
              effect: "filter",
              filter: { visible: true },
            },
          ],
        },
      ],
    },
  },
}
```

**Result:** Anonymous users only see filtered records; authenticated users see all.

### Step 6: Mixed Public/Owner Access

Public can read published; owners can read their own drafts:

```typescript
{
  roles: {
    anonymous: {
      is_default: true,
      implicit_allow: false,
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            condition: { entity: "posts" },
            effect: "filter",
            filter: { status: "published" },
          }],
        },
      ],
    },
    user: {
      implicit_allow: false,
      permissions: [
        // Read: published OR own posts
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            condition: { entity: "posts" },
            effect: "filter",
            filter: {
              $or: [
                { status: "published" },
                { author_id: "@user.id" },
              ],
            },
          }],
        },
        // Create allowed
        "data.entity.create",
        // Update own only
        {
          permission: "data.entity.update",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: { author_id: "@user.id" },
          }],
        },
      ],
    },
  },
}
```

### Step 7: Invite-Only System

No public access, no self-registration:

```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
    },
  },
  options: {
    seed: async (ctx) => {
      // Admin creates users manually
      await ctx.app.module.auth.createUser({
        email: "[email protected]",
        password: "admin-password",
        role: "admin",
      });
    },
  },
}
```

### Step 8: API with Public Read, Auth Write

Common REST API pattern:

```typescript
{
  roles: {
    anonymous: {
      is_default: true,
      implicit_allow: false,
      permissions: ["data.entity.read"],  // Read anything
    },
    api_user: {
      implicit_allow: false,
      permissions: [
        "data.entity.read",
        "data.entity.create",
        "data.entity.update",
        "data.entity.delete",
      ],
    },
  },
}
```

## Complete Configuration Examples

### Blog Platform

```typescript
import { serve } from "bknd/adapter/bun";
import { em, entity, text, boolean, relation } from "bknd";

const schema = em(
  {
    posts: entity("posts", {
      title: text().required(),
      content: text(),
      published: boolean().default(false),
    }),
    comments: entity("comments", {
      body: text().required(),
      approved: boolean().default(false),
    }),
    users: entity("users", {}),
  },
  ({ posts, comments, users }) => [
    relation(posts, "author").manyToOne(users),
    relation(comments, "post").manyToOne(posts),
    relation(comments, "user").manyToOne(users),
  ]
);

serve({

Related in General