Skip to main content
You already have users. You just added agents. This is the ten-minute recipe.
KavachOS does not replace your human auth. It runs alongside it. This guide assumes you already have a way to get a stable userId for every request.

What you need before you start

A user ID on every request

Your existing auth must expose a stable string ID, anything from Clerk’s userId, Auth.js’s session.user.id, or a row id from your own users table.

A database

Postgres, MySQL, SQLite, or Cloudflare D1. Kavach creates its own tables, prefixed kavach_, alongside yours.

Steps

1

Install

pnpm add kavachos @kavachos/nextjs
Substitute the adapter for your framework: @kavachos/hono, @kavachos/express, @kavachos/fastify, and so on. See Framework adapters.
2

Create the Kavach instance

lib/kavach.ts
import { createKavach } from 'kavachos';

let instance: Awaited<ReturnType<typeof createKavach>> | null = null;

export async function getKavach() {
  if (!instance) {
    instance = await createKavach({
      database: {
        provider: 'postgres',
        url: process.env.DATABASE_URL!,
      },
      secret: process.env.KAVACHOS_SECRET!,
    });
  }
  return instance;
}
The lazy pattern lets Next.js build without opening a DB connection.
3

Wire the route handler

app/api/kavach/[...kavach]/route.ts
import { kavachNextjs } from '@kavachos/nextjs';
import { getKavach } from '@/lib/kavach';

const { GET, POST } = kavachNextjs(getKavach);

export { GET, POST };
Pick a path that does not collide with your existing auth. /api/kavach/* lives next to /api/auth/* (Clerk / Auth.js) without stepping on it.
4

Create an agent when a user signs up or activates AI features

Hook into your existing post-sign-in flow. For Clerk, that is a webhook or a server action. For Auth.js, the signIn event. For better-auth, the onSignIn hook.
wherever you create user-scoped resources
import { getKavach } from '@/lib/kavach';

export async function createDefaultAgent(userId: string) {
  const kavach = await getKavach();

  const agent = await kavach.agent.create({
    ownerId: userId,                     // stable ID from your auth provider
    name: 'default',
    type: 'autonomous',
    permissions: [
      { resource: 'app:read:*', actions: ['read'] },
    ],
  });

  // Persist agent.token somewhere the user can access, encrypted at rest.
  // It is returned once and cannot be recovered.
  return { agentId: agent.id, token: agent.token };
}
The token is shown once. Store it in your secrets store or hand it directly to the agent process. If you lose it, rotate with kavach.agent.rotate(agentId) to issue a new one.
5

Authorize in your handlers

Anywhere your agent code runs, check authorization before the call.
app/api/agent-action/route.ts
import { getKavach } from '@/lib/kavach';

export async function POST(req: Request) {
  const token = req.headers.get('Authorization')?.replace('Bearer ', '');
  if (!token) return new Response('Missing token', { status: 401 });

  const kavach = await getKavach();

  const { allowed, reason, auditId } = await kavach.authorizeByToken(token, {
    action: 'read',
    resource: 'mcp:github:repos',
  });

  if (!allowed) {
    return Response.json({ error: reason, auditId }, { status: 403 });
  }

  // allowed. Do the work.
  const repos = await fetchGithubRepos();
  return Response.json({ repos, auditId });
}

Fitting in with Clerk, Auth.js, and better-auth

import { auth } from '@clerk/nextjs/server';
import { getKavach } from '@/lib/kavach';

export async function POST(req: Request) {
  const { userId } = await auth();
  if (!userId) return new Response('Unauthorized', { status: 401 });

  const kavach = await getKavach();
  const agent = await kavach.agent.create({
    ownerId: userId,
    name: 'default',
    type: 'autonomous',
    permissions: [{ resource: 'app:read:*', actions: ['read'] }],
  });

  return Response.json({ agentId: agent.id, token: agent.token });
}

Common first-day questions

No by default. Kavach runs its own migrations on first boot. Set skipMigrations: true in createKavach if you want to run them yourself via kavach-cli migrate.
Kavach stores ownerId as a string and never interprets it. Migrating from numeric IDs to UUIDs later means updating kavach_agents.owner_id in a single SQL update.
Yes, via event streaming and webhooks. Wire them to your analytics or to Stripe / PostHog / Slack.

Next steps

Permissions

Define what agents can and cannot do.

MCP OAuth 2.1

Run your own authorization server for MCP tools.

Delegation

Let agents spawn sub-agents with scoped permissions.

Audit trail

Query and export every authorization decision.
Last modified on April 20, 2026