Express Middleware
Sign it. Prove it. -- in your Express app.
JACS provides jacsMiddleware for Express v4/v5 that verifies incoming signed request bodies and optionally auto-signs JSON responses. No body-parser gymnastics, no monkey-patching.
5-Minute Quickstart
1. Install
npm install @hai.ai/jacs express
2. Create a JACS client
import { JacsClient } from '@hai.ai/jacs/client';
const client = await JacsClient.quickstart({
name: 'my-agent',
domain: 'my-agent.example.com',
});
3. Add signing middleware
import express from 'express';
import { jacsMiddleware } from '@hai.ai/jacs/express';
const app = express();
app.use(express.text({ type: 'application/json' }));
app.use(jacsMiddleware({ client, verify: true }));
app.post('/api/data', (req, res) => {
console.log(req.jacsPayload); // verified payload
res.json({ status: 'ok' });
});
app.listen(3000);
Quick Start
import express from 'express';
import { JacsClient } from '@hai.ai/jacs/client';
import { jacsMiddleware } from '@hai.ai/jacs/express';
const client = await JacsClient.quickstart({
name: 'my-agent',
domain: 'my-agent.example.com',
});
const app = express();
app.use(express.text({ type: 'application/json' }));
app.use(jacsMiddleware({ client, verify: true }));
app.post('/api/data', (req, res) => {
console.log(req.jacsPayload); // verified payload
res.json({ status: 'ok' });
});
app.listen(3000);
Options
jacsMiddleware({
client?: JacsClient; // Pre-initialized client (preferred)
configPath?: string; // Path to jacs.config.json (if no client)
sign?: boolean; // Auto-sign res.json() responses (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
};
})
If neither client nor configPath is provided, the middleware initializes a client with JacsClient.quickstart({ name: 'jacs-express', domain: 'localhost' }) on first request.
What the Middleware Does
Every request gets req.jacsClient -- a JacsClient instance you can use for manual signing/verification in route handlers.
POST/PUT/PATCH with verify: true (default): The string body is verified as a JACS document. On success, req.jacsPayload contains the extracted payload. On failure, a 401 is returned (unless optional: true).
With sign: true: res.json() is intercepted to auto-sign the response body before sending.
Verify Incoming Requests
app.use(express.text({ type: 'application/json' }));
app.use(jacsMiddleware({ client }));
app.post('/api/process', (req, res) => {
if (!req.jacsPayload) {
return res.status(400).json({ error: 'Missing payload' });
}
const { action, data } = req.jacsPayload;
res.json({ processed: true, action });
});
With optional: true, unsigned requests pass through with req.jacsPayload unset:
app.use(jacsMiddleware({ client, optional: true }));
app.post('/api/mixed', (req, res) => {
if (req.jacsPayload) {
// Verified JACS request
res.json({ verified: true, data: req.jacsPayload });
} else {
// Unsigned request -- handle accordingly
res.json({ verified: false });
}
});
Auth Replay Protection (Auth Endpoints)
Enable replay protection when signed JACS bodies are used as authentication artifacts:
app.use(
jacsMiddleware({
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
Enable sign: true to intercept res.json() calls:
app.use(jacsMiddleware({ client, sign: true }));
app.post('/api/data', (req, res) => {
// This response will be JACS-signed automatically
res.json({ result: 42, timestamp: new Date().toISOString() });
});
Manual Signing in Routes
Use req.jacsClient for fine-grained control:
app.use(jacsMiddleware({ client }));
app.post('/api/custom', async (req, res) => {
const result = processData(req.jacsPayload);
// Sign manually
const signed = await req.jacsClient.signMessage(result);
res.type('application/json').send(signed.raw);
});
Per-Route Middleware
Apply JACS to specific routes only:
const app = express();
const jacs = jacsMiddleware({ client });
// Public routes -- no JACS
app.get('/health', (req, res) => res.json({ status: 'ok' }));
// Protected routes
app.use('/api', express.text({ type: 'application/json' }), jacs);
app.post('/api/secure', (req, res) => {
res.json({ data: req.jacsPayload });
});
Multiple Agents
Use different JacsClient instances per route group:
const adminClient = await JacsClient.quickstart({
name: 'admin-agent',
domain: 'admin.example.com',
algorithm: 'pq2025',
});
const userClient = await JacsClient.quickstart({
name: 'user-agent',
domain: 'user.example.com',
algorithm: 'ring-Ed25519',
});
app.use('/admin', express.text({ type: 'application/json' }));
app.use('/admin', jacsMiddleware({ client: adminClient }));
app.use('/user', express.text({ type: 'application/json' }));
app.use('/user', jacsMiddleware({ client: userClient }));
Migration from JACSExpressMiddleware
The legacy JACSExpressMiddleware from @hai.ai/jacs/http still works but is deprecated. To migrate:
| Old | New |
|---|---|
import { JACSExpressMiddleware } from '@hai.ai/jacs/http' | import { jacsMiddleware } from '@hai.ai/jacs/express' |
JACSExpressMiddleware({ configPath: '...' }) | jacsMiddleware({ configPath: '...' }) |
| Per-request agent init | Shared client, lazy-loaded once |
res.send() monkey-patch | res.json() interception (opt-in) |
The new middleware is simpler, faster (no per-request init), and gives you req.jacsClient for manual operations.
Next Steps
- Koa Middleware - Same pattern for Koa
- HTTP Server - Core HTTP integration concepts
- Vercel AI SDK - AI model provenance signing
- API Reference - Complete API documentation