@rootherald/node
The server-side SDK. Default path: appraise an opaque evidence blob server→server with rh.verify() and get the verdict back directly. Offline/badge path: verify a portable token with verifyAttestationToken().
Install
npm install @rootherald/nodeAppraise evidence (Background-Check default)
The default RATS Background-Check flow: your dumb client collects an opaque evidence blob and hands it to your server; your server calls Root Herald server→server with its rh_sk_ secret key. Two calls (mint a challenge, then appraise), and the verdict comes back directly with no token to verify.
import { RootHerald, QuotaExceededError } from "@rootherald/node";
const rh = new RootHerald({ secretKey: process.env.RH_SECRET_KEY! }); // rh_sk_…
// (a) GET /api/challenge — mint a nonce and relay it to your client.
export async function GET() {
const { challengeId, nonce } = await rh.issueChallenge();
return Response.json({ challengeId, nonce });
}
// (b) POST /api/signup — the client posts back the opaque evidence; appraise it.
export async function POST(req: Request) {
const { challengeId, evidence, email, password } = await req.json();
let verdict;
try {
verdict = await rh.verify(evidence, {
challengeId,
policy: "rootherald:builtin:strict-hardware",
});
} catch (err) {
// Throws only on protocol/auth/quota problems, not on a failing device.
if (err instanceof QuotaExceededError) {
return Response.json({ error: "rate_limited" }, { status: 429 });
}
throw err;
}
// A real-but-rejected device returns a verdict; branch on device.verdict.
if (verdict.device.verdict !== "pass") {
return Response.json({ error: "device_rejected" }, { status: 403 });
}
// verdict.device.ueid is a stable device UUID. Use it for
// one-device-one-account enforcement.
if (await usersRepo.deviceAlreadyRegistered(verdict.device.ueid)) {
return Response.json({ error: "device_already_registered" }, { status: 409 });
}
const user = await usersRepo.create({
email,
password,
deviceId: verdict.device.ueid,
});
return Response.json({ ok: true, userId: user.id });
}Verify a portable token (offline / badge path)
If you opt into a portable token (rh.verify(evidence, { …, returnToken: true })), or you're on the backend-less badge tier where a keyless SPA collects evidence and the RootHerald server — reached with your origin-pinned publishable rh_pk_ site key — returns the signed token (the client holds no secret and signs nothing), verify it offline with verifyAttestationToken(). It fetches the issuer's JWKS, verifies the JWT signature, checks iss / aud / expiry, and returns the same typed AttestationVerdict. It throws on a bad token rather than returning a failure verdict:
import {
verifyAttestationToken,
TokenExpiredError,
InvalidTokenError,
} from "@rootherald/node";
const verdict = await verifyAttestationToken(token, {
issuer: "https://rootherald.io/acme",
audience: process.env.RH_PROJECT_ID!,
}).catch((err) => {
if (err instanceof TokenExpiredError) throw new Response(null, { status: 401 });
if (err instanceof InvalidTokenError) throw new Response(null, { status: 403 });
throw err;
});
// Same AttestationVerdict shape as rh.verify() returns.
const ueid = verdict.device.ueid;Gate a route (Express middleware — token path)
For routes where the client carries a portable token (the badge path, or after you opt into returnToken), requireAttestation returns an Express-compatible middleware. It verifies the bearer token, enforces an assurance floor and freshness window, and attaches the verdict at req.attestation — or responds 401with an RFC 9470 step-up challenge when the token doesn't clear the bar. (Under the Background-Check default you appraise with rh.verify() instead.)
import express from "express";
import { requireAttestation } from "@rootherald/node";
const app = express();
// Require a fresh, high-assurance device attestation on money-movement.
app.post(
"/api/transfer",
requireAttestation({
issuer: "https://rootherald.io/acme",
audience: process.env.RH_PROJECT_ID!,
acrValues: ["urn:rootherald:device:high"],
maxAgeSeconds: 300,
}),
(req, res) => {
// req.attestation is the verified, typed AttestationVerdict.
res.json({ ok: true, device: req.attestation!.device.ueid });
},
);