Claude
Skills
Sign in
Back

bknd-protect-endpoint

Included with Lifetime
$97 forever

Use when securing specific API endpoints in Bknd. Covers protecting custom HTTP triggers, plugin routes, auth middleware for Flows, checking permissions in custom endpoints, and role-based endpoint access.

Backend & APIs

What this skill does


# Protect Endpoint

Secure specific API endpoints with authentication and authorization checks.

## Prerequisites

- Bknd project with code-first configuration
- Auth enabled (`auth: { enabled: true }`)
- Guard enabled for authorization (`guard: { enabled: true }`)
- Roles defined (see **bknd-create-role**)

## When to Use UI Mode

- Viewing registered routes: Admin Panel > System > Debug
- Inspecting role permissions

**Note:** Endpoint protection requires code mode. UI is read-only.

## When to Use Code Mode

- Creating protected custom endpoints
- Adding auth checks to HTTP triggers
- Building protected plugin routes
- Implementing endpoint-specific permissions

## Code Approach

### Understanding Endpoint Types

Bknd has several endpoint types to protect:

| Type | Path Pattern | How to Protect |
|------|--------------|----------------|
| Data API | `/api/data/*` | Guard permissions (automatic) |
| Auth API | `/api/auth/*` | Built-in protection |
| Media API | `/api/media/*` | Guard permissions (automatic) |
| HTTP Triggers | Custom paths | Manual auth check |
| Plugin Routes | Custom paths | Manual auth check |

### Step 1: Protect HTTP Trigger (Flow)

Add authentication to a custom endpoint via FunctionTask:

```typescript
import { serve } from "bknd/adapter/bun";
import { Flow, HttpTrigger, FunctionTask } from "bknd";

// Protected endpoint flow
const protectedFlow = new Flow("protected-endpoint", [
  new FunctionTask({
    name: "checkAuth",
    handler: async (input, ctx) => {
      // ctx.app gives access to modules
      const authModule = ctx.app.modules.get("auth");
      const user = await authModule.authenticator.getUserFromRequest(input);

      if (!user) {
        throw new Response(JSON.stringify({ error: "Unauthorized" }), {
          status: 401,
          headers: { "Content-Type": "application/json" },
        });
      }

      // Pass user to next task
      return { user, body: await input.json() };
    },
  }),
  new FunctionTask({
    name: "processRequest",
    handler: async (input) => {
      // input contains { user, body } from previous task
      return {
        message: `Hello ${input.user.email}`,
        data: input.body,
      };
    },
  }),
]);

protectedFlow.setTrigger(
  new HttpTrigger({
    path: "/api/custom/protected",
    method: "POST",
    respondWith: "processRequest",
  })
);

serve({
  connection: { url: "file:data.db" },
  config: {
    flows: {
      flows: [protectedFlow],
    },
  },
});
```

### Step 2: Protect Plugin Route

Add auth check in plugin's `onServerInit`:

```typescript
import { serve } from "bknd/adapter/bun";
import { createPlugin } from "bknd";

const protectedPlugin = createPlugin({
  name: "protected-routes",

  onServerInit: (server) => {
    // Protected endpoint
    server.post("/api/custom/data", async (c) => {
      // Get app from context
      const app = c.get("app");
      const authModule = app.modules.get("auth");

      // Resolve user from request
      const user = await authModule.authenticator.getUserFromRequest(c.req.raw);

      if (!user) {
        return c.json({ error: "Unauthorized" }, 401);
      }

      // Proceed with protected logic
      const body = await c.req.json();
      return c.json({
        message: "Protected data",
        user: user.email,
        received: body,
      });
    });

    // Public endpoint (no auth check)
    server.get("/api/custom/public", (c) => {
      return c.json({ message: "Public data" });
    });
  },
});

serve({
  connection: { url: "file:data.db" },
  plugins: [protectedPlugin],
});
```

### Step 3: Role-Based Endpoint Protection

Check user's role for specific permissions:

```typescript
const roleProtectedPlugin = createPlugin({
  name: "role-protected",

  onServerInit: (server) => {
    // Admin-only endpoint
    server.delete("/api/admin/users/:id", async (c) => {
      const app = c.get("app");
      const authModule = app.modules.get("auth");
      const user = await authModule.authenticator.getUserFromRequest(c.req.raw);

      // Check authentication
      if (!user) {
        return c.json({ error: "Unauthorized" }, 401);
      }

      // Check role
      if (user.role !== "admin") {
        return c.json({ error: "Forbidden: Admin role required" }, 403);
      }

      // Proceed with admin action
      const userId = c.req.param("id");
      // ... delete user logic
      return c.json({ deleted: userId });
    });
  },
});
```

### Step 4: Permission-Based Protection with Guard

Use Guard for granular permission checks:

```typescript
import { createPlugin, DataPermissions } from "bknd";

const guardProtectedPlugin = createPlugin({
  name: "guard-protected",

  onServerInit: (server) => {
    server.post("/api/custom/sync", async (c) => {
      const app = c.get("app");
      const authModule = app.modules.get("auth");
      const guard = authModule.guard;

      const user = await authModule.authenticator.getUserFromRequest(c.req.raw);

      if (!user) {
        return c.json({ error: "Unauthorized" }, 401);
      }

      // Check specific permission using Guard
      try {
        guard.granted(
          DataPermissions.databaseSync,  // Permission to check
          { role: user.role },           // User context
          {}                             // Permission context
        );
      } catch (error) {
        return c.json({
          error: "Forbidden",
          message: error.message,
        }, 403);
      }

      // User has permission - proceed
      return c.json({ status: "sync started" });
    });
  },
});
```

### Step 5: Entity-Specific Permission Check

Check permissions for specific entity operations:

```typescript
server.post("/api/custom/posts/batch", async (c) => {
  const app = c.get("app");
  const authModule = app.modules.get("auth");
  const guard = authModule.guard;

  const user = await authModule.authenticator.getUserFromRequest(c.req.raw);

  if (!user) {
    return c.json({ error: "Unauthorized" }, 401);
  }

  // Check create permission for posts entity
  try {
    guard.granted(
      DataPermissions.entityCreate,
      { role: user.role },
      { entity: "posts" }  // Entity-specific context
    );
  } catch (error) {
    return c.json({
      error: "Cannot create posts",
      message: error.message,
    }, 403);
  }

  // Proceed with batch creation
  const body = await c.req.json();
  // ... create posts
  return c.json({ created: body.length });
});
```

### Step 6: Reusable Auth Middleware

Create a helper for consistent auth checks:

```typescript
// auth-middleware.ts
type AuthContext = {
  user: any;
  role: string;
};

export async function requireAuth(
  c: any,
  app: any
): Promise<AuthContext | Response> {
  const authModule = app.modules.get("auth");
  const user = await authModule.authenticator.getUserFromRequest(c.req.raw);

  if (!user) {
    return c.json({ error: "Unauthorized" }, 401);
  }

  return { user, role: user.role };
}

export async function requireRole(
  c: any,
  app: any,
  allowedRoles: string[]
): Promise<AuthContext | Response> {
  const result = await requireAuth(c, app);

  if (result instanceof Response) {
    return result;
  }

  if (!allowedRoles.includes(result.role)) {
    return c.json({
      error: "Forbidden",
      required: allowedRoles,
      current: result.role,
    }, 403);
  }

  return result;
}

// Usage in plugin
server.get("/api/reports/admin", async (c) => {
  const app = c.get("app");
  const auth = await requireRole(c, app, ["admin", "manager"]);

  if (auth instanceof Response) return auth;

  // auth.user available
  return c.json({ reports: [] });
});
```

### Step 7: Protecting Flow with Auth Task

Create reusable auth task for Flows:

```typescript
import { Flow, HttpTrigger, FunctionTask } from "bknd";

// Reusable auth task
const authTask = new FunctionTask({
  name: "requireAuth",
  handler: async (input, ctx) => {
    const authModule = ctx.app.modules.get("auth");
    const user

Related in Backend & APIs