HTTP Server
JACS provides middleware and utilities for building HTTP servers with cryptographic request/response signing. This enables secure communication between JACS agents over HTTP.
Overview
JACS HTTP integration provides:
- Request signing: Sign outgoing HTTP requests with your agent's key
- Request verification: Verify incoming requests were signed by a valid agent
- Response signing: Automatically sign responses before sending
- Response verification: Verify server responses on the client side
- Framework middleware: Ready-to-use middleware for Express and Koa
Core Concepts
Request/Response Flow
Client Server
| |
|-- signRequest(payload) -----> |
| |-- verifyResponse() --> payload
| |-- process payload
| |-- signResponse(result)
|<-- verifyResponse(result) ---|
|
All messages are cryptographically signed, ensuring:
- Message integrity (no tampering)
- Agent identity (verified sender)
- Non-repudiation (proof of origin)
HTTP Client
Basic Client Usage
import jacs from 'jacsnpm';
import http from 'http';
async function main() {
// Load JACS agent
await jacs.load('./jacs.config.json');
// Prepare payload
const payload = {
message: "Hello, secure server!",
data: { id: 123, value: "some data" },
timestamp: new Date().toISOString()
};
// Sign the request
const signedRequest = await jacs.signRequest(payload);
// Send HTTP request
const response = await sendRequest(signedRequest, 'localhost', 3000, '/api/echo');
// Verify the response
const verifiedResponse = await jacs.verifyResponse(response);
console.log('Verified payload:', verifiedResponse.payload);
}
function sendRequest(body, host, port, path) {
return new Promise((resolve, reject) => {
const options = {
hostname: host,
port: port,
path: path,
method: 'POST',
headers: {
'Content-Type': 'text/plain',
'Content-Length': Buffer.byteLength(body),
},
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(data);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
}
});
});
req.on('error', reject);
req.write(body);
req.end();
});
}
main();
Using Fetch
import jacs from 'jacsnpm';
async function sendJacsRequest(url, payload) {
await jacs.load('./jacs.config.json');
// Sign the payload
const signedRequest = await jacs.signRequest(payload);
// Send request
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: signedRequest
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
// Verify response
const responseText = await response.text();
const verified = await jacs.verifyResponse(responseText);
return verified.payload;
}
// Usage
const result = await sendJacsRequest('http://localhost:3000/api/data', {
action: 'fetch',
query: { id: 42 }
});
Express Server
Using Express Middleware
JACS provides JACSExpressMiddleware that automatically:
- Verifies incoming JACS requests
- Attaches the verified payload to
req.jacsPayload - Signs outgoing responses when you call
res.send()with an object
import express from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';
const app = express();
const PORT = 3000;
// IMPORTANT: Use express.text() BEFORE JACS middleware
// This ensures req.body is a string for JACS verification
app.use('/api', express.text({ type: '*/*' }));
// Apply JACS middleware to API routes
app.use('/api', JACSExpressMiddleware({
configPath: './jacs.server.config.json'
}));
// Route handler
app.post('/api/echo', (req, res) => {
// Access verified payload from req.jacsPayload
const payload = req.jacsPayload;
if (!payload) {
return res.status(400).send('JACS payload missing');
}
console.log('Received verified payload:', payload);
// Send response object - middleware will sign it automatically
res.send({
echo: "Server says hello!",
received: payload,
timestamp: new Date().toISOString()
});
});
app.listen(PORT, () => {
console.log(`JACS Express server listening on port ${PORT}`);
});
Middleware Configuration
JACSExpressMiddleware({
configPath: './jacs.config.json' // Required: path to JACS config
})
Manual Request/Response Handling
For more control, you can handle signing manually:
import express from 'express';
import jacs from 'jacsnpm';
const app = express();
// Initialize JACS once at startup
await jacs.load('./jacs.config.json');
app.use(express.text({ type: '*/*' }));
app.post('/api/process', async (req, res) => {
try {
// Manually verify incoming request
const verified = await jacs.verifyResponse(req.body);
const payload = verified.payload;
// Process the request
const result = {
success: true,
data: processData(payload),
timestamp: new Date().toISOString()
};
// Manually sign the response
const signedResponse = await jacs.signResponse(result);
res.type('text/plain').send(signedResponse);
} catch (error) {
console.error('JACS verification failed:', error);
res.status(400).send('Invalid JACS request');
}
});
Koa Server
Using Koa Middleware
import Koa from 'koa';
import { JACSKoaMiddleware } from 'jacsnpm/http';
const app = new Koa();
const PORT = 3000;
// Apply JACS Koa middleware
// Handles raw body reading, verification, and response signing
app.use(JACSKoaMiddleware({
configPath: './jacs.server.config.json'
}));
// Route handler
app.use(async (ctx) => {
if (ctx.path === '/api/echo' && ctx.method === 'POST') {
// Access verified payload from ctx.state.jacsPayload or ctx.jacsPayload
const payload = ctx.state.jacsPayload || ctx.jacsPayload;
if (!payload) {
ctx.status = 400;
ctx.body = 'JACS payload missing';
return;
}
console.log('Received verified payload:', payload);
// Set response object - middleware will sign it automatically
ctx.body = {
echo: "Koa server says hello!",
received: payload,
timestamp: new Date().toISOString()
};
} else {
ctx.status = 404;
ctx.body = 'Not Found. Try POST to /api/echo';
}
});
app.listen(PORT, () => {
console.log(`JACS Koa server listening on port ${PORT}`);
});
API Reference
jacs.signRequest(payload)
Sign an object as a JACS request.
const signedRequest = await jacs.signRequest({
method: 'getData',
params: { id: 123 }
});
// Returns: JACS-signed JSON string
jacs.verifyResponse(responseString)
Verify a JACS-signed response and extract the payload.
const result = await jacs.verifyResponse(jacsResponseString);
// Returns: { payload: {...}, jacsId: '...', ... }
const payload = result.payload;
jacs.signResponse(payload)
Sign an object as a JACS response.
const signedResponse = await jacs.signResponse({
success: true,
data: { result: 42 }
});
// Returns: JACS-signed JSON string
JACSExpressMiddleware(options)
Express middleware for JACS request/response handling.
import { JACSExpressMiddleware } from 'jacsnpm/http';
app.use(JACSExpressMiddleware({
configPath: './jacs.config.json' // Required
}));
Request Processing:
- Reads
req.bodyas a JACS string - Verifies the signature
- Attaches payload to
req.jacsPayload - On failure, sends 400 response
Response Processing:
- Intercepts
res.send()calls - If body is an object, signs it as JACS
- Sends signed string to client
JACSKoaMiddleware(options)
Koa middleware for JACS request/response handling.
import { JACSKoaMiddleware } from 'jacsnpm/http';
app.use(JACSKoaMiddleware({
configPath: './jacs.config.json' // Required
}));
Request Processing:
- Reads raw request body
- Verifies JACS signature
- Attaches payload to
ctx.state.jacsPayloadandctx.jacsPayload
Response Processing:
- Signs
ctx.bodyif it's an object - Converts to JACS string before sending
Complete Example
Server (server.js)
import express from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';
const app = express();
// JACS middleware for /api routes
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({
configPath: './jacs.server.config.json'
}));
// Echo endpoint
app.post('/api/echo', (req, res) => {
const payload = req.jacsPayload;
res.send({
echo: payload,
serverTime: new Date().toISOString()
});
});
// Calculate endpoint
app.post('/api/calculate', (req, res) => {
const { operation, a, b } = req.jacsPayload;
let result;
switch (operation) {
case 'add': result = a + b; break;
case 'subtract': result = a - b; break;
case 'multiply': result = a * b; break;
case 'divide': result = a / b; break;
default: return res.status(400).send({ error: 'Unknown operation' });
}
res.send({ operation, a, b, result });
});
app.listen(3000, () => console.log('Server running on port 3000'));
Client (client.js)
import jacs from 'jacsnpm';
async function main() {
await jacs.load('./jacs.client.config.json');
// Call echo endpoint
const echoResult = await callApi('/api/echo', {
message: 'Hello, server!'
});
console.log('Echo result:', echoResult);
// Call calculate endpoint
const calcResult = await callApi('/api/calculate', {
operation: 'multiply',
a: 7,
b: 6
});
console.log('Calculate result:', calcResult);
}
async function callApi(path, payload) {
const signedRequest = await jacs.signRequest(payload);
const response = await fetch(`http://localhost:3000${path}`, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: signedRequest
});
const responseText = await response.text();
const verified = await jacs.verifyResponse(responseText);
return verified.payload;
}
main().catch(console.error);
Security Considerations
Content-Type
JACS requests should use text/plain content type since they are signed JSON strings, not raw JSON.
Error Handling
Always handle verification failures gracefully:
try {
const verified = await jacs.verifyResponse(responseText);
return verified.payload;
} catch (error) {
console.error('JACS verification failed:', error.message);
// Handle invalid/tampered response
}
Agent Keys
Each server and client needs its own JACS agent with:
- Unique agent ID
- Private/public key pair
- Configuration file pointing to the keys
Middleware Order
For Express, ensure express.text() comes before JACSExpressMiddleware:
// Correct order
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ configPath: '...' }));
// Wrong - JACS middleware won't receive string body
app.use('/api', JACSExpressMiddleware({ configPath: '...' }));
app.use('/api', express.text({ type: '*/*' }));
Next Steps
- Express Middleware - More Express integration patterns
- MCP Integration - Model Context Protocol support
- API Reference - Complete API documentation