How to add amesh to your existing application. Pick the recipe that matches your setup.
Your Server (target) <--WebSocket--> Relay <--WebSocket--> Client (controller) Both sides verify a 6-digit code, then exchange public keys. Trust is one-way: controller → target. The relay can be shut down after this.
Controller ----HTTP + AuthMesh header----> Target One-way. No relay. Stateless headers. Target cannot call back.
Add amesh signing and verification to an Express API.
npm install @authmesh/sdk express// server.ts import express from 'express'; import { amesh } from '@authmesh/sdk'; const app = express(); app.use(express.text({ type: '*/*' })); // Checks signature, timestamp, nonce, allow list app.use('/api', amesh.verify()); app.post('/api/orders', (req, res) => { res.json({ device: req.authMesh.deviceId, name: req.authMesh.friendlyName, }); }); app.listen(3000);
// client.ts import { amesh } from '@authmesh/sdk'; const res = await amesh.fetch('http://localhost:3000/api/orders', { method: 'POST', body: JSON.stringify({ amount: 100 }), });
# Install CLI npm install -g @authmesh/cli # Create identity on each machine amesh init --name "prod-api" # Pair: server is the target, your laptop is the controller amesh listen # on server (target) amesh invite 482916 # on laptop (controller — use code from listen) # Done. Trust is one-way: laptop → server. Relay can be stopped.
When your server is in the cloud, both machines need to reach the same relay.
# On the remote server (SSH in) amesh listen --relay wss://relay.authmesh.dev/ws # On your laptop amesh invite 482916 --relay wss://relay.authmesh.dev/ws
# On the remote server bunx @authmesh/relay # starts on :3001 amesh listen --relay ws://localhost:3001/ws # On your laptop amesh invite 482916 --relay ws://your-server:3001/ws
# On your laptop — generate a token amesh provision --name "prod-server" --ttl 1h # Set on remote server as env var. SDK auto-pairs on first request. AMESH_BOOTSTRAP_TOKEN=eyJ... node app.js
Each service gets its own device identity. Pair once, authenticate every request.
// Service B (the API) app.use(amesh.verify()); app.get('/internal/users/:id', (req, res) => { console.log(`Request from ${req.authMesh.friendlyName}`); res.json({ id: req.params.id }); }); // Service A (the caller) const res = await amesh.fetch('http://service-b:4000/internal/users/123');
For multi-instance deployments behind a load balancer. Prevents replay attacks across instances.
import { amesh } from '@authmesh/sdk'; import { RedisNonceStore } from '@authmesh/sdk/redis'; app.use(amesh.verify({ nonceStore: new RedisNonceStore(process.env.REDIS_URL), }));
Sign webhooks with device identity instead of a shared secret.
// Sending await amesh.fetch(webhookUrl, { method: 'POST', body: JSON.stringify(event), }); // Receiving app.post('/webhooks', amesh.verify(), (req, res) => { console.log(`Webhook from ${req.authMesh.friendlyName}`); res.sendStatus(200); });
Use the same code paths in local dev as production. No environment checks needed.
# Create local identities (encrypted-file backend for dev) AUTH_MESH_DIR=/tmp/amesh-a amesh init --name "local-service-a" --backend encrypted-file AUTH_MESH_DIR=/tmp/amesh-b amesh init --name "local-service-b" --backend encrypted-file # Pair them (same ceremony as production) AUTH_MESH_DIR=/tmp/amesh-b amesh listen # on service-b (target) AUTH_MESH_DIR=/tmp/amesh-a amesh invite 482916 # on service-a (controller)
// Same code in dev and production. No environment checks. import {'{'} amesh {'}'} from '@authmesh/sdk'; const res = await amesh.fetch('http://localhost:4000/api/data', {'{'} method: 'POST', body: JSON.stringify({'{'} query: 'test' {'}'}) {'}'});
Production: macOS Keychain, Secure Enclave, or TPM 2.0
Local dev: --backend encrypted-file (passphrase auto-generated)
AUTH_MESH_DIR AUTH_MESH_PASSPHRASE AMESH_BOOTSTRAP_TOKEN AMESH_RELAY_URL REDIS_URL // Available on req.authMesh after amesh.verify() interface AuthMeshIdentity { deviceId: string; // "am_cOixWcOdI8-pLh4P" friendlyName: string; // "prod-api" verifiedAt: number; // Unix timestamp } // amesh.verify() options interface VerifyOptions { clockSkewSeconds?: number; // Default: 30 nonceWindowSeconds?: number; // Default: 60 nonceStore?: NonceStore; // Default: InMemoryNonceStore }
amesh list), (2) clocks are within 30s, (3) body is parsed as text (express.text()), not JSON.