You have a Next.js App Router app. It does something useful with AI. Now a user asks: “Can I log in? Can I give the agent access to my GitHub without handing it my password?” Good questions. Here is how to answer them in about ten minutes.
The goal is two things working at once. First, a human signs in with passkeys. Second, the agent gets its own identity with scoped permissions that trace back to that human. No shared API keys. No service accounts sitting on top of your prod database.
01
Project setup
Start with a fresh Next.js install or drop this into an existing project. The only hard requirement is App Router. Pages Router will need minor adjustments to the middleware pattern.
npx create-next-app@latest my-agent-app --typescript --app
cd my-agent-app
npm install kavachosCreate a project at app.kavachos.com/sign-up and copy your API key. Then add it to .env.local:
KAVACHOS_API_KEY=kv_live_xxxxxxxxxxxxxxxxxxxxNext, create a single kavachOS instance you can import anywhere. I put this in lib/kavach.ts so it is never initialized twice:
import { createKavach } from 'kavachos';
if (!process.env.KAVACHOS_API_KEY) {
throw new Error('KAVACHOS_API_KEY is not set');
}
export const kavach = createKavach({
apiKey: process.env.KAVACHOS_API_KEY,
});That is the whole setup. The SDK connects to kavachOS Cloud and keeps the connection warm. Everything else builds on this one export.
02
Human login with passkeys
Passkeys are the fastest path to production-quality auth. No password reset emails, no TOTP codes to copy. The browser handles the credential. Here is the route handler that starts a passkey registration:
import { NextRequest, NextResponse } from 'next/server';
import { kavach } from '@/lib/kavach';
export async function POST(req: NextRequest) {
try {
const { email } = await req.json() as { email: string };
const options = await kavach.auth.passkey.beginRegistration({
email,
// displayName is shown in the OS passkey prompt
displayName: email.split('@')[0],
});
return NextResponse.json(options);
} catch (error) {
console.error('[passkey register]', error);
return NextResponse.json({ error: 'Failed to begin registration' }, { status: 500 });
}
}The finish step takes the credential from the browser and mints a session:
import { NextRequest, NextResponse } from 'next/server';
import { kavach } from '@/lib/kavach';
import { cookies } from 'next/headers';
export async function POST(req: NextRequest) {
const body = await req.json();
const { session } = await kavach.auth.passkey.finishRegistration(body);
// Set the session cookie (httpOnly, sameSite strict)
const cookieStore = await cookies();
cookieStore.set('kv_session', session.token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: session.expiresIn,
path: '/',
});
return NextResponse.json({ userId: session.userId });
}The sign-in flow mirrors this: one route to begin authentication, one to finish it. The Next.js adapter guide has the full browser-side code using the @simplewebauthn/browser client library.
Now wire up middleware so authenticated routes stay protected. A single matcher covers everything under /dashboard:
import { NextRequest, NextResponse } from 'next/server';
import { kavach } from '@/lib/kavach';
export async function middleware(req: NextRequest) {
const token = req.cookies.get('kv_session')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', req.url));
}
const { valid, userId } = await kavach.sessions.verify(token);
if (!valid) {
return NextResponse.redirect(new URL('/login', req.url));
}
const res = NextResponse.next();
res.headers.set('x-user-id', userId);
return res;
}
export const config = {
matcher: ['/dashboard/:path*'],
};03
Creating an agent identity
The user is logged in. Now we need to give the agent its own credentials. This is the part that most auth libraries skip entirely. They let you pass the user session token directly to the agent. That works until the user revokes access or the agent needs narrower permissions than the user has.
kavachOS treats agents as first-class principals. Each agent gets an ID, a set of permissions, and a link back to the user who authorized it. Here is a route handler that creates an agent when a user starts a new session:
import { NextRequest, NextResponse } from 'next/server';
import { kavach } from '@/lib/kavach';
export async function POST(req: NextRequest) {
const userId = req.headers.get('x-user-id');
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const agent = await kavach.agents.create({
name: 'coding-assistant',
// Permissions are scoped strings you define. The format is yours.
permissions: ['read:repos', 'read:issues', 'write:comments'],
delegatedFrom: userId,
// Agent token expires after 2 hours of inactivity
ttl: '2h',
});
return NextResponse.json({
agentId: agent.id,
token: agent.token,
});
}Store agent.token in your session store or pass it to the AI SDK as a header. The token identifies the agent uniquely. When you verify it server-side, you get both the agent ID and the originating user ID back. The audit trail records both. See agent identity for the full permission model and expiry behavior.
04
Wiring to a route handler
Now the agent needs to call your app. It sends its token in the Authorization header. The route handler verifies it and checks permissions before doing any work:
import { NextRequest, NextResponse } from 'next/server';
import { kavach } from '@/lib/kavach';
export async function POST(req: NextRequest) {
const authHeader = req.headers.get('authorization');
const token = authHeader?.replace('Bearer ', '');
if (!token) {
return NextResponse.json({ error: 'Missing token' }, { status: 401 });
}
const identity = await kavach.agents.verify(token);
if (!identity.valid) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
// Check the agent actually has this permission
if (!identity.permissions.includes('read:repos')) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const { query } = await req.json() as { query: string };
// Your actual business logic here
const results = await searchRepositories(query, identity.delegatedFrom);
return NextResponse.json({ results });
}
async function searchRepositories(query: string, userId: string) {
// Fetch using the user's connected OAuth token
// ...
return [];
}The pattern is the same for every route the agent touches. Verify the token, check the specific permission, then proceed. Three lines of guard code before any business logic runs.
You can also pull React hooks into client components for session state. The Next.js adapter guide covers useSession and useAgentIdentity, both of which work server-side too via getServerSideSession.
05
Optional: delegation to sub-agents
Sometimes one agent spawns others. A research agent might create a summarizer and a citation checker. Each child should have narrower permissions than its parent and a shorter TTL. kavachOS enforces this automatically: you cannot delegate permissions the parent does not have.
import { NextRequest, NextResponse } from 'next/server';
import { kavach } from '@/lib/kavach';
export async function POST(req: NextRequest) {
const authHeader = req.headers.get('authorization');
const parentToken = authHeader?.replace('Bearer ', '');
if (!parentToken) {
return NextResponse.json({ error: 'Missing token' }, { status: 401 });
}
const parent = await kavach.agents.verify(parentToken);
if (!parent.valid) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
// Create a child agent with a subset of the parent's permissions
const child = await kavach.agents.delegate({
from: parent.agentId,
// Must be a subset of parent.permissions
permissions: ['read:repos'],
ttl: '30m',
purpose: 'code-search-subtask',
});
return NextResponse.json({ token: child.token });
}The delegation chain is visible in the audit trail. If anything goes wrong mid-chain, you can trace exactly which agent made which call, in what order, and with what permissions active at the time. The delegation guide covers depth limits and how to revoke a chain mid-run.
Topics
- #Next.js auth
- #AI agent auth
- #kavachOS tutorial
- #agent identity
- #App Router auth
Keep going in the docs
Start
Quickstart
Install kavachOS, wire up your first route, and issue your first token in under five minutes.
Reference
Next.js adapter
Wire kavachOS into the Next.js App Router with middleware, route handlers, and React hooks.
Agents
Agent identity
Cryptographic bearer tokens, wildcard permissions, and per-agent budgets. The core primitive.
Reference
Framework adapters
Drop-in adapters for Hono, Express, Next.js, Fastify, Nuxt, SvelteKit, Astro, and NestJS.
Read next
- Tutorial
Building an MCP tool server with auth, end to end
Scaffold the server, wire up OAuth, write tools, test with Claude. Complete code, no skipped steps.
15 min read - Engineering
Agent delegation chains: the pattern everyone gets wrong
Most teams hand agents a shared API key and call it delegation. Here is what the correct model looks like and why it matters at runtime.
8 min read
Share this post
Get started
Try kavachOS Cloud free
Free up to 1,000 MAU. Agent identities and MCP OAuth on every plan.