Auth libraries handle human sign-in. But what happens when your AI agent needs to read from GitHub, deploy to production, or call another agent's API? You end up building a custom permissions layer, rolling your own tokens, and hoping your audit trail holds up.
I got tired of building that layer from scratch for every project, so I built KavachOS. It handles both human auth and agent identity in one library. Here's how to get it running.
What you'll build
┌─────────────────────────────────────────────┐
│ Your App │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Human │ │ Agent │ │ Agent │ │
│ │ (Ada) │ │ (reader) │ │ (deploy) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ KavachOS Auth Layer │ │
│ │ sessions · tokens · permissions · │ │
│ │ delegation · audit · MCP OAuth │ │
│ └──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ SQLite / │ │
│ │ Postgres │ │
│ └──────────┘ │
└─────────────────────────────────────────────┘
A Node.js API where humans sign in with email/password, AI agents get their own identity with scoped permissions, and every action is authorized and logged.
About 5 minutes if you type fast.
Setup
mkdir my-app && cd my-app
npm init -y
npm install kavachos @kavachos/hono hono
Create index.ts:
import { createKavach } from "kavachos";
import { emailPassword } from "kavachos/auth";
import { createHonoAdapter } from "@kavachos/hono";
import { Hono } from "hono";
const kavach = await createKavach({
database: { provider: "sqlite", url: "kavach.db" },
plugins: [emailPassword()],
});
const app = new Hono();
app.route("/api/auth", createHonoAdapter(kavach));
That gives you a full auth API. Sign-up, sign-in, sign-out, session management. The SQLite database is created automatically.
Create a user
curl -X POST http://localhost:3000/api/auth/sign-up/email \
-H "Content-Type: application/json" \
-d '{"email": "ada@example.com", "password": "secret123", "name": "Ada"}'
Response:
{
"data": {
"token": "kv_sess_abc123...",
"user": { "id": "user-1", "email": "ada@example.com", "name": "Ada" }
}
}
Now the interesting part: agent identity
This is where KavachOS is different from other auth libraries. You can give AI agents their own identity with scoped permissions.
// Create an agent that can read GitHub repos but needs approval to deploy
const agent = await kavach.agent.create({
ownerId: "user-1",
name: "github-reader",
type: "autonomous",
permissions: [
{ resource: "mcp:github:*", actions: ["read"] },
{
resource: "mcp:deploy:production",
actions: ["execute"],
constraints: { requireApproval: true },
},
],
});
console.log(agent.token); // kv_agent_xyz789...
The agent gets a cryptographic bearer token and a set of permissions. The permissions use wildcard matching, so mcp:github:* matches mcp:github:repos, mcp:github:issues, and anything else under that namespace.
How wildcard matching works
Permission: mcp:github:*
mcp:github:repos ✅ match
mcp:github:issues ✅ match
mcp:github:pulls ✅ match
mcp:deploy:staging ❌ no match
mcp:slack:send ❌ no match
Authorize agent actions
Before your agent does something, check if it's allowed:
const result = await kavach.authorize(agent.id, {
action: "read",
resource: "mcp:github:repos",
});
if (result.allowed) {
// Go ahead, read the repos
console.log("Authorized. Audit ID:", result.auditId);
} else {
console.log("Denied:", result.reason);
}
Every authorization check is logged with an audit ID. You can trace what any agent did, when, and whether it was allowed.
Delegation chains
Agents can delegate permissions to other agents, with limits:
Ada (human)
└── github-reader (agent)
permissions: [mcp:github:* → read, mcp:deploy:* → execute]
│
└── repo-scanner (sub-agent)
permissions: [mcp:github:repos → read]
↑ subset of parent's permissions only
const subAgent = await kavach.agent.create({
ownerId: agent.id, // owned by another agent, not a human
name: "repo-scanner",
type: "autonomous",
permissions: [
{ resource: "mcp:github:repos", actions: ["read"] },
],
});
The sub-agent can only have permissions that are a subset of its parent's permissions. If the parent agent gets revoked, all its sub-agents lose access too. You can also set depth limits to prevent chains from going too deep.
Wire it to MCP
If you're using the Model Context Protocol, KavachOS ships an OAuth 2.1 authorization server:
import { mcpOAuth } from "kavachos/mcp";
const kavach = await createKavach({
database: { provider: "sqlite", url: "kavach.db" },
plugins: [emailPassword(), mcpOAuth()],
});
This gives you PKCE S256, dynamic client registration (RFC 7591), and resource indicators (RFC 8707). Your MCP tools can authenticate through standard OAuth flows.
What the MCP OAuth flow looks like
MCP Client KavachOS MCP Server
│ │ │
│ 1. Authorization Request │ │
│ (PKCE + code_challenge) │ │
│ ─────────────────────────► │ │
│ │ │
│ 2. Auth Code │ │
│ ◄───────────────────────── │ │
│ │ │
│ 3. Token Exchange │ │
│ (code + code_verifier) │ │
│ ─────────────────────────► │ │
│ │ │
│ 4. Access Token │ │
│ ◄───────────────────────── │ │
│ │ │
│ 5. API Call with Token │ │
│ ──────────────────────────────────────────────────────► │
│ │ │
Add more auth methods
Want magic links? Passkeys? OAuth with Google? They're all plugins:
import {
emailPassword,
magicLink,
passkey,
totp,
} from "kavachos/auth";
const kavach = await createKavach({
database: { provider: "postgres", url: process.env.DATABASE_URL },
plugins: [
emailPassword(),
magicLink({
sendMagicLink: async (email, url) => {
await resend.emails.send({
to: email,
subject: "Sign in to MyApp",
html: `<a href="${url}">Click to sign in</a>`,
});
},
}),
passkey(),
totp(),
],
});
All 14 auth methods
| Method | Plugin | Notes |
|---|---|---|
| Email + password | emailPassword() |
HIBP breach checking built in |
| Magic link | magicLink() |
Bring your own email sender |
| Email OTP | emailOtp() |
6-digit code via email |
| Phone SMS | phoneOtp() |
Bring your own SMS sender |
| Passkey/WebAuthn | passkey() |
Passwordless biometric |
| TOTP 2FA | totp() |
Google Authenticator, Authy |
| Anonymous | anonymous() |
Guest sessions |
| Google One-tap | googleOneTap() |
One-click sign-in |
| Username/password | usernamePassword() |
For non-email systems |
| Sign In With Ethereum | siwe() |
Web3 wallet auth |
| Device authorization | deviceAuth() |
TV/CLI flows (RFC 8628) |
| Captcha | captcha() |
Turnstile, reCAPTCHA |
| Password reset | passwordReset() |
Signed, expiring tokens |
| Session freshness | sessionFreshness() |
Re-auth for sensitive ops |
Framework adapters
KavachOS runs on whatever you're using:
| Framework | Install | Adapter |
|---|---|---|
| Hono | npm i @kavachos/hono |
createHonoAdapter(kavach) |
| Express | npm i @kavachos/express |
createExpressRouter(kavach) |
| Next.js | npm i @kavachos/nextjs |
createNextjsHandler(kavach) |
| Fastify | npm i @kavachos/fastify |
kavachPlugin(kavach) |
| Nuxt | npm i @kavachos/nuxt |
createNuxtHandler(kavach) |
| SvelteKit | npm i @kavachos/sveltekit |
createSvelteKitHandler(kavach) |
| Astro | npm i @kavachos/astro |
createAstroHandler(kavach) |
| NestJS | npm i @kavachos/nestjs |
KavachModule.register(kavach) |
| SolidStart | npm i @kavachos/solidstart |
createSolidHandler(kavach) |
| TanStack Start | npm i @kavachos/tanstack |
createTanStackHandler(kavach) |
React hooks
On the frontend:
import { KavachProvider, useSession, useSignIn } from "@kavachos/react";
function App() {
return (
<KavachProvider>
<LoginPage />
</KavachProvider>
);
}
function LoginPage() {
const { signIn } = useSignIn();
const { session } = useSession();
if (session) return <p>Signed in as {session.user.email}</p>;
return (
<button onClick={() => signIn("ada@example.com", "secret123")}>
Sign in
</button>
);
}
Also available: @kavachos/vue, @kavachos/svelte, @kavachos/expo (React Native), @kavachos/electron.
Deploy to the edge
Three runtime dependencies: drizzle-orm, jose, zod. That's it. Runs on Cloudflare Workers, Deno, Bun, and Node without code changes.
Runtime support:
Node.js 20+ ✅
Cloudflare Workers ✅ (D1 database)
Deno ✅
Bun ✅
Dependencies:
drizzle-orm ORM + query builder
jose JWT signing/verification
zod Schema validation
Total: 3
// Cloudflare Workers + D1
const kavach = await createKavach({
database: { provider: "d1", binding: env.KAVACH_DB },
plugins: [emailPassword()],
});
What's next
KavachOS is MIT licensed and open source. The full docs are at docs.kavachos.com.
Code: github.com/kavachos/kavachos
Top comments (0)