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.
Supported Routes
Middleware works on HTTP, WebSocket, SSE, Cron, and Stream routes.
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;Authentication Middleware
The @agentuity/auth package provides authentication middleware:
import { createRouter } from '@agentuity/runtime';
import { createSessionMiddleware } from '@agentuity/auth';
import { auth } from '../auth';
const router = createRouter();
router.get('/public', (c) => {
return c.json({ message: 'Public endpoint' });
});
// Protected route requiring authentication
router.get('/protected', createSessionMiddleware(auth), async (c) => {
const user = await c.var.auth.getUser();
return c.json({
userId: user.id,
email: user.email,
name: user.name,
});
});
export default router;The @agentuity/auth package automatically integrates with useAPI and useWebsocket hooks, injecting auth tokens into requests.
Full Authentication Setup
For complete setup including client-side configuration, see Adding Authentication.
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;Built-in Middleware
Agentuity provides middleware for common needs, exported from @agentuity/runtime.
CORS
Apply CORS headers to routes:
import { createRouter, createCorsMiddleware } from '@agentuity/runtime';
const router = createRouter();
// Apply CORS to all API routes
router.use('/api/*', createCorsMiddleware());
// Or with custom options
router.use('/api/*', createCorsMiddleware({
origin: ['https://myapp.com', 'https://admin.myapp.com'],
credentials: true,
maxAge: 3600,
}));
export default router;CORS can also be configured globally via createApp(). See App Configuration.
Compression
Compress responses with gzip/deflate based on the Accept-Encoding header:
import { createRouter, createCompressionMiddleware } from '@agentuity/runtime';
const router = createRouter();
// Apply compression to all routes
router.use('/*', createCompressionMiddleware());
// Or with custom threshold (only compress responses > 2KB)
router.use('/*', createCompressionMiddleware({
threshold: 2048,
}));
export default router;Compression automatically bypasses:
- WebSocket upgrade requests
- Requests without
Accept-Encodingheaders - SSE and binary streams
Compression can also be configured globally via createApp({ compression: { ... } }). See App Configuration.
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!