Koa Middleware
Sign it. Prove it. -- in your Koa app.
JACS provides jacsKoaMiddleware for Koa with the same design as the Express middleware -- verify incoming signed bodies, optionally auto-sign responses.
Quick Start
import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import { JacsClient } from '@hai.ai/jacs/client';
import { jacsKoaMiddleware } from '@hai.ai/jacs/koa';
const client = await JacsClient.quickstart({
name: 'my-agent',
domain: 'my-agent.example.com',
});
const app = new Koa();
app.use(bodyParser({ enableTypes: ['text'] }));
app.use(jacsKoaMiddleware({ client, verify: true }));
app.use(async (ctx) => {
console.log(ctx.state.jacsPayload); // verified payload
ctx.body = { status: 'ok' };
});
app.listen(3000);
Options
jacsKoaMiddleware({
client?: JacsClient; // Pre-initialized client (preferred)
configPath?: string; // Path to jacs.config.json (if no client)
sign?: boolean; // Auto-sign ctx.body after next() (default: false)
verify?: boolean; // Verify incoming POST/PUT/PATCH bodies (default: true)
optional?: boolean; // Allow unsigned requests through (default: false)
authReplay?: boolean | { // Replay protection for auth-style endpoints (default: false)
enabled?: boolean;
maxAgeSeconds?: number; // default: 30
clockSkewSeconds?: number; // default: 5
cacheTtlSeconds?: number; // default: maxAge + skew
};
})
How It Works
Every request gets ctx.state.jacsClient for manual use.
POST/PUT/PATCH with verify: true: The string body is verified. On success, ctx.state.jacsPayload is set. On failure, 401 is returned (unless optional: true).
With sign: true: After downstream middleware runs, if ctx.body is a non-Buffer object, it is signed before the response is sent.
Auth Replay Protection (Auth Endpoints)
Enable replay protection when signed JACS bodies are used as authentication artifacts:
app.use(
jacsKoaMiddleware({
client,
verify: true,
authReplay: { enabled: true, maxAgeSeconds: 30, clockSkewSeconds: 5 },
})
);
When enabled, middleware enforces:
- signature timestamp freshness (
maxAgeSeconds+clockSkewSeconds) - single-use
(signerId, signature)dedupe inside a TTL cache
Notes:
- Keep this mode scoped to auth-style endpoints.
- Cache is in-memory per process; use a shared cache for multi-instance deployments.
Auto-Sign Responses
app.use(jacsKoaMiddleware({ client, sign: true }));
app.use(async (ctx) => {
// This will be JACS-signed automatically after next()
ctx.body = { result: 42, timestamp: new Date().toISOString() };
});
Manual Signing
app.use(jacsKoaMiddleware({ client }));
app.use(async (ctx) => {
const result = processData(ctx.state.jacsPayload);
const signed = await ctx.state.jacsClient.signMessage(result);
ctx.type = 'application/json';
ctx.body = signed.raw;
});
Comparison with Express
| Feature | Express | Koa |
|---|---|---|
| Import | jacsMiddleware from @hai.ai/jacs/express | jacsKoaMiddleware from @hai.ai/jacs/koa |
| Client access | req.jacsClient | ctx.state.jacsClient |
| Payload | req.jacsPayload | ctx.state.jacsPayload |
| Auto-sign target | res.json() interception | ctx.body after next() |
Next Steps
- Express Middleware - Express version
- Vercel AI SDK - AI model provenance signing
- API Reference - Complete API documentation