Middleware & Authentication
Add authentication, validation, and request processing to your routes
Agentuity routes are built on Hono, giving you access to Hono's middleware system. Use middleware to add authentication, logging, CORS, rate limiting, and other shared request processing.
Works Everywhere
Middleware works on all route types in src/api/. The patterns below apply to HTTP, WebSocket, SSE, and email routes.
Middleware Compatibility
Middleware is supported on: HTTP routes, WebSocket, SSE, Email, and Stream routes. SMS and Cron routes do not support middleware.
Basic Middleware
Create middleware with createMiddleware from Hono:
import { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
const router = createRouter();
// Middleware that logs all requests
const loggerMiddleware = createMiddleware(async (c, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
c.var.logger.info('Request completed', {
method: c.req.method,
path: c.req.path,
duration,
});
});
// Apply to specific route
router.get('/data', loggerMiddleware, (c) => {
return c.json({ data: 'value' });
});
export default router;Authentication Patterns
API Key Authentication
import { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
const router = createRouter();
const apiKeyAuth = createMiddleware(async (c, next) => {
const apiKey = c.req.header('X-API-Key');
if (!apiKey) {
return c.json({ error: 'API key required' }, 401);
}
// Validate against stored keys
const keyData = await c.var.kv.get('api-keys', apiKey);
if (!keyData.exists) {
return c.json({ error: 'Invalid API key' }, 401);
}
// Add user info to context for downstream handlers
c.set('apiKeyOwner', keyData.data.ownerId);
await next();
});
router.get('/protected', apiKeyAuth, (c) => {
const ownerId = c.var.apiKeyOwner;
return c.json({ message: 'Access granted', ownerId });
});
export default router;Bearer Token (JWT)
import { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
import { verify } from 'hono/jwt';
const router = createRouter();
const jwtAuth = createMiddleware(async (c, next) => {
const authHeader = c.req.header('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
return c.json({ error: 'Bearer token required' }, 401);
}
const token = authHeader.slice(7);
try {
const payload = await verify(token, process.env.JWT_SECRET!);
c.set('user', payload);
await next();
} catch {
return c.json({ error: 'Invalid token' }, 401);
}
});
router.get('/me', jwtAuth, (c) => {
const user = c.var.user;
return c.json({ userId: user.sub, email: user.email });
});
export default router;Third-Party Auth Providers
Services like Clerk, Auth0, and Supabase Auth provide drop-in authentication. Here's an example with Clerk:
import { createRouter } from '@agentuity/runtime';
import { clerkMiddleware, getAuth } from '@hono/clerk-auth';
const router = createRouter();
// Apply Clerk middleware
router.use('/*', clerkMiddleware());
router.get('/public', (c) => {
return c.json({ message: 'Public endpoint' });
});
router.get('/protected', (c) => {
const auth = getAuth(c);
if (!auth?.userId) {
return c.json({ error: 'Unauthorized' }, 401);
}
return c.json({ userId: auth.userId });
});
export default router;Using Other Providers
For other auth providers, check if they have Hono middleware available, or use JWT validation with their tokens.
Request Validation
Use agent.validator() for type-safe request validation:
import { createRouter } from '@agentuity/runtime';
import agent from './agent';
const router = createRouter();
// Validates using agent's input schema
router.post('/items', agent.validator(), async (c) => {
const data = c.req.valid('json'); // Fully typed from schema
await c.var.kv.set('items', crypto.randomUUID(), data);
return c.json({ created: true, item: data });
});
export default router;Custom Schema Override
Pass a schema to override the agent's default:
import { z } from 'zod';
const customSchema = z.object({
name: z.string().min(1).max(100),
price: z.number().positive(),
category: z.enum(['electronics', 'clothing', 'food']),
});
router.post('/custom',
agent.validator({ input: customSchema }),
async (c) => {
const data = c.req.valid('json');
return c.json(data);
}
);With Valibot
The validator works with any StandardSchema-compatible library:
import * as v from 'valibot';
const itemSchema = v.object({
name: v.pipe(v.string(), v.minLength(1), v.maxLength(100)),
price: v.pipe(v.number(), v.minValue(0)),
category: v.picklist(['electronics', 'clothing', 'food']),
});
router.post('/items',
agent.validator({ input: itemSchema }),
async (c) => {
const data = c.req.valid('json');
return c.json(data);
}
);See Schema Libraries for more on Valibot and ArkType.
Route-Level Middleware
Apply middleware to all routes in a router with router.use():
import { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
const router = createRouter();
// Apply to all routes in this file
router.use('/*', createMiddleware(async (c, next) => {
c.var.logger.info('API request', { path: c.req.path });
await next();
}));
// Apply to specific path prefix
router.use('/admin/*', adminAuthMiddleware);
router.get('/public', (c) => c.text('Anyone can access'));
router.get('/admin/users', (c) => c.json({ users: [] })); // Requires admin auth
export default router;Combining Middleware
Chain multiple middleware for complex requirements:
import { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
import agent from './agent';
const router = createRouter();
const rateLimiter = createMiddleware(async (c, next) => {
const ip = c.req.header('x-forwarded-for') || 'unknown';
const key = `ratelimit:${ip}`;
const result = await c.var.kv.get<number>('ratelimit', key);
const count = result.exists ? result.data : 0;
if (count >= 100) {
return c.json({ error: 'Rate limit exceeded' }, 429);
}
await c.var.kv.set('ratelimit', key, count + 1, { ttl: 60 });
await next();
});
const authMiddleware = createMiddleware(async (c, next) => {
const token = c.req.header('Authorization')?.slice(7);
if (!token) {
return c.json({ error: 'Unauthorized' }, 401);
}
c.set('userId', await validateToken(token));
await next();
});
// Chain: rate limit → auth → validation → handler
router.post('/message',
rateLimiter,
authMiddleware,
agent.validator(),
async (c) => {
const userId = c.var.userId;
const { message } = c.req.valid('json');
return c.json({ received: true, userId, message });
}
);
export default router;Error Handling
Handle errors gracefully in middleware:
import { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
const router = createRouter();
const errorHandler = createMiddleware(async (c, next) => {
try {
await next();
} catch (error) {
c.var.logger.error('Unhandled error', {
error: error instanceof Error ? error.message : String(error),
path: c.req.path,
});
return c.json(
{ error: 'Internal server error' },
500
);
}
});
router.use('/*', errorHandler);
router.get('/risky', (c) => {
// If this throws, errorHandler catches it
throw new Error('Something went wrong');
});
export default router;Best Practices
- Order matters: Middleware runs in registration order. Put error handling first, then auth, then validation.
- Early returns: Return immediately on failure (don't call
next()). - Share data via context: Use
c.set('key', value)to pass data to downstream handlers, access withc.var.key. - Keep middleware focused: Each middleware should do one thing.
Next Steps
- HTTP Routes: Complete routing reference
- Calling Agents from APIs: Invoke agents from API routes
Need Help?
Join our Community for assistance or just to hang with other humans building agents.
Send us an email at hi@agentuity.com if you'd like to get in touch.
Please Follow us on
If you haven't already, please Signup for your free account now and start building your first agent!