@mandate-security/node
v0.2.0
Published
MANDATE token verification for Node.js: local X-Mndt checks plus optional hosted validation for high-value paths.
Maintainers
Readme
@mandate-security/node
Verification of MANDATE tokens for Node.js. Add one script tag on the frontend, let the browser runtime attach the opaque X-Mndt header on protected same-origin requests, and verify tokens locally on your backend with your site secret. For high-value actions, call hosted validation to add replay, session-continuity, and origin checks.
npm scope:
@mandate-security— the@mandatescope is registered by an unrelated project on npm.
Requires Node.js 18+.
Quick start
1. Frontend (browser)
Add the MANDATE script before calling routes your backend protects. Public builds keep tokens private to the verifier runtime and attach X-Mndt automatically on same-origin protected requests.
<script src="https://gate.x-lock.dev/g.js?k=YOUR_SITE_KEY" async></script>const res = await fetch("/api/checkout", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ cartId: "..." }), });
### 2. Backend (Node)
```bash
npm install @mandate-security/node
# or, from a repo checkout:
npm install /path/to/mandate/sdks/nodeSet env vars (shown once in the MANDATE dashboard):
MANDATE_SITE_SECRET=mss_live_... # server-only
MANDATE_SITE_KEY=sk_live_... # optional but recommended
MANDATE_MIN_SCORE=0.75 # optionalVerify a token:
import { verifyRequest } from "@mandate-security/node";
const result = verifyRequest(req.headers, {
siteSecret: process.env.MANDATE_SITE_SECRET,
siteKey: process.env.MANDATE_SITE_KEY,
minScore: 0.75,
});
if (!result.success) {
return res.status(403).json({ error: result.error });
}Or bind env vars once:
import { createVerifier } from "@mandate-security/node";
const mandate = createVerifier(); // reads MANDATE_* env vars
export function requireHuman(req) {
const result = mandate.verifyRequest(req.headers);
if (!result.human) throw new Error(result.error ?? "forbidden");
return result;
}For high-value actions such as login, checkout, billing, admin changes, or API mutations, add hosted validation. This still keeps normal request verification local, but asks the gate to check server-side replay/session/origin continuity for the sensitive action:
import { validateHostedRequest } from "@mandate-security/node";
const result = await validateHostedRequest(req.headers, {
siteSecret: process.env.MANDATE_SITE_SECRET,
siteKey: process.env.MANDATE_SITE_KEY,
gateUrl: process.env.MANDATE_GATE_URL, // optional; defaults to https://gate.x-lock.dev
highValue: true,
path: req.path,
origin: "https://app.example.com",
});
if (!result.success) {
return res.status(403).json({ error: result.error });
}Express
import express from "express";
import { mandate } from "@mandate-security/node/express";
const app = express();
app.use(mandate({
siteSecret: process.env.MANDATE_SITE_SECRET,
siteKey: process.env.MANDATE_SITE_KEY,
mode: "observe", // log decisions first; switch to "enforce" when ready
minScore: 0.75,
onDecision: (d, req) => console.log({ path: req.path, score: d.score, action: d.action }),
}));
app.post("/api/checkout", (req, res) => {
if (!req.mandate.human) {
return res.status(403).json({ error: req.mandate.error });
}
// ...
});Observe first, then enforce: start in mode: "observe" so every request proceeds and req.mandate is always set. When you trust the scores on real traffic, flip to mode: "enforce" to respond 403 automatically.
Next.js (App Router)
// app/api/checkout/route.js
import { withMandate } from "@mandate-security/node/next";
export const POST = withMandate(
async (_request, { mandate }) => {
if (!mandate.human) {
return Response.json({ error: mandate.error }, { status: 403 });
}
return Response.json({ ok: true });
},
{
siteSecret: process.env.MANDATE_SITE_SECRET,
siteKey: process.env.MANDATE_SITE_KEY,
mode: "observe",
},
);In mode: "enforce", failed verification returns 403 before your handler runs.
API reference
verify(token, options) / verifyMandateToken(token, options)
Verify a raw token string. Fully local — HMAC-SHA256 + AES-GCM, no network.
verifyRequest(headers, options)
Read X-Mndt from Fetch Headers, Express req, or a plain header map, then verify.
createVerifier(options?)
Returns { verify, verifyRequest, validateHosted, validateHostedRequest, readToken, header }. Reads MANDATE_SITE_SECRET, MANDATE_SITE_KEY, MANDATE_MIN_SCORE, and optional MANDATE_GATE_URL when options are omitted.
validateHosted(token, options) / validateHostedRequest(headers, options)
Calls hosted POST /validate for high-value paths that need server-side replay, session-continuity, and origin checks. Options include gateUrl, validateUrl, siteSecret, siteKey, minScore, highValue, endpointValue, path, endpoint, route, origin, and siteOrigin.
When available from trusted edge/backend metadata, also pass clientIP, ipAddress, asn, and country so MANDATE can score token replay across network changes without making local verification phone home.
Result shape
{
success: boolean;
action: "allow" | "challenge" | "block" | null;
score: number | null; // 0.0 (bot) to 1.0 (human)
human: boolean; // success && action === "allow"
siteKey: string | null;
sessionId: string | null;
expiresAt: number | null; // unix seconds
error?: "missing_token" | "invalid_token" | "expired_token"
| "wrong_site_key" | "below_min_score" | "blocked" | "challenge";
}Notes
- Tokens are opaque encrypted binary (not JWTs) with a 15-minute TTL.
- Local verification keeps no replay state. Use hosted validation for high-value paths that need MANDATE's replay/session/token-context checks.
- Never expose your site secret to the client.
Docs
Full documentation: x-lock.dev/docs
Generate an integration guide for your app:
./mandate init --target ./your-app --framework expressLicense
MIT
