Documentation embedded in this build.
Using authboxjs
If you are writing an application that sits behind authboxproxy, holds its own mTLS certificate, or is a relying party for AuthBox's OIDC provider, authboxjs gives you typed calls for AuthBox's served surfaces plus the small hand-written pieces typing alone cannot provide — receipt verification, transport, honest errors. It is not for the console: the console is server-rendered, and its own progressive-enhancement scripts consume this same build from inside the binary rather than through a published package (docs/plans/042 §1's fences, and its G5 amendment). This page is for your application's code.
Everything here has a raw fetch/https equivalent shown beside it. That is a promise, not a caveat: authboxjs removes friction, but the server grows nothing only this library can reach, and you can always drop back to the raw call if you would rather not carry the dependency at all.
Install from your own deployment
The SDK is published nowhere — no npm registry, no separate download site. It ships inside AuthBox itself, under the Apache-2.0 license, served on the same no-certificate listener as this guide and the redacted API specification. Download it from the deployment you are integrating with:
curl --cacert ca.pem -O https://authbox.example:8080/sdk/authboxjs.tgz
tar tzf authboxjs.tgz | head # see what's inside before you trust it
npm install ./authboxjs.tgz
The archive is assembled deterministically from the exact build you are talking to, so client and server can never drift apart by more than a deployment upgrade — and the version inside package.json tells you which build packaged it.
typescript is the package's sole devDependency and it ships zero runtime dependencies of its own — no bundler, no framework, nothing transitive to audit.
The typed client, in ten lines
import { Transport, fetchAdapter, SelfClient } from "authboxjs";
const transport = new Transport({
baseUrl: "https://authbox.example",
adapter: fetchAdapter(), // the default; shown for clarity
});
const whoami = new SelfClient(transport);
const me = await whoami.selfWhoami();
console.log(me.dn, me.attributes);
In a browser, fetchAdapter() uses the platform's own fetch, which already carries whatever client certificate the browser imported — this library does no credential handling of its own; it types what you already have.
Raw equivalent, no library at all:
const res = await fetch("https://authbox.example/self/v1/whoami");
if (!res.ok) throw new Error(`request failed: ${res.status} ${await res.text()}`);
const me = await res.json();
gen/*.ts covers every served document worth a client: SelfClient, the administrative AuthboxClient, OidcClient (the mutual-TLS-listener endpoints only — see below), and ClearanceClient. Everything is generated from the redacted view of AuthBox's own OpenAPI documents, so nothing here can leak an authorization detail the published artifact was never supposed to carry.
A Node caller doing mTLS itself (rather than talking to a proxy over plain HTTP) needs nodeHttpsAdapter instead of fetchAdapter(), because Node's global fetch has no per-request way to attach a client certificate. See sdk/authboxjs/README.md's "Node and mutual TLS" section for the full shape; the pattern is the same Transport, a different adapter underneath.
Verifying a decision receipt — and where it actually goes
If your application sits behind authboxproxy, every permitted request the proxy forwards to you carries a signed decision receipt, X-AuthBox-Receipt — a compact JWS naming the caller's subject, mission, and an allowlisted set of attributes, bound to your route and a short expiry. The full definition, claim by claim, is the decision receipt profile in AuthBox's own documentation.
A receipt that verifies is not necessarily a permit. Since profile authbox-rcpt/2, a refused caller gets a receipt too, and it says so in an explicit outcome claim. verifyReceipt reads that claim and refuses a denial by default, so the code below is unchanged from what you would have written before and is still correct. If you find yourself reaching for outcome: "any", you are writing an inspector, not an authorization check.
The receipt is addressed to you, the upstream application — never echoed back to the original caller. The proxy sets this header on the forwarded request it sends to your service, not on the response it returns to whoever it authenticated. If you were expecting to read it off a Response object in your own frontend code, that is the wrong place to look: your server, receiving the proxy's forwarded request, reads it off the incoming request's headers.
import { importJWKS, verifyReceipt } from "authboxjs";
// The JWKS document your proxy operator distributes — read once, from wherever
// authboxproxy -export-jwks wrote it, or from a configured jwks_path.
const jwksDoc = JSON.parse(await readFile("authbox-receipt-jwks.json", "utf8"));
const keys = await importJWKS(jwksDoc);
// In your own request handler, receiving what the proxy forwarded:
const compact = req.headers.get("x-authbox-receipt");
const claims = await verifyReceipt(compact, { keys, route: "my-route" });
// claims.sub, claims.mission, and claims.attributes are now trustworthy — this
// is the proxy's decision, verified, even if the hop between it and you happens
// to be plain HTTP. It threw if the receipt said "deny".
Raw equivalent, no library at all:
const [h, p, sig] = compact.split(".");
const b64 = (s) => Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
const header = JSON.parse(new TextDecoder().decode(b64(h)));
const jwk = jwksDoc.keys.find((k) => k.kid === header.kid);
const key = await crypto.subtle.importKey("jwk", jwk, { name: "ECDSA", namedCurve: jwk.crv }, false, ["verify"]);
const ok = await crypto.subtle.verify(
{ name: "ECDSA", hash: "SHA-256" }, key, b64(sig), new TextEncoder().encode(`${h}.${p}`));
const claims = JSON.parse(new TextDecoder().decode(b64(p)));
if (!ok) throw new Error("receipt does not verify");
if (claims.token_use !== "proxied-request") throw new Error("not a proxy decision receipt");
if ((claims.outcome ?? "permit") !== "permit") throw new Error("the receipt says deny");
if (claims.aud !== "my-route") throw new Error("receipt is bound to another route");
if (Date.now() / 1000 >= claims.exp) throw new Error("receipt expired");
That is the whole contract — WebCrypto, the JWKS document's plain coordinates, and four claim comparisons. Note the fourth line from the bottom: the ?? "permit" is the migration rule for a profile-v1 receipt, which carries no outcome at all and could only ever have been minted for a permit. You lose nothing but the typing and the error messages.
Verifying gets you the proxy's decision integrity independent of transport security on that hop. verifyReceipt is a thin, optional convenience over three checks you can make by hand with nothing but WebCrypto and the JWKS document's plain coordinates — src/receipt.ts is under 100 lines specifically so reading it is a legitimate alternative to trusting it.
Logging in with OIDC — and the /token reality
services/oidc/openapi.yaml declares only /token, /userinfo, and /delegated-token — the mutual-TLS-listener endpoints OidcClient covers. /authorize, /jwks, and discovery live on the unauthenticated listener and are outside any authorization map, so src/oidc.ts is hand-written to mirror that contract directly.
State this plainly before you design your relying party: redeeming a code at /token is RFC 8705 §2 mutual-TLS client authentication — the registered application's own certificate, never a client_id/client_secret body. A browser can present only the resource owner's ambient certificate (exactly what /authorize consumes); it cannot also present the application's certificate to redeem the code. So the code exchange is Node/confidential- client territory, done server-side with nodeHttpsAdapter and the application's own certificate — not a browser capability this library merely discourages, but one it cannot provide because the protocol does not allow it.
The sketch:
import {
generatePKCE, buildAuthorizeUrl, exchangeCode, validateIdToken,
nodeHttpsAdapter, importJWKS, userinfo, Transport,
} from "authboxjs";
// 1. Browser: build the URL and navigate — the resource owner's own certificate
// is ambient here. This library never fetches /authorize itself.
const pkce = await generatePKCE();
const nonce = crypto.randomUUID();
const url = buildAuthorizeUrl({
authorizationEndpoint: "https://authbox.example/oidc/v1/authorize",
clientId: "CN=my-app,O=acme,C=US",
redirectUri: "https://my-app.example/callback",
codeChallenge: pkce.challenge,
nonce,
});
// window.location.href = url; ... lands back at redirectUri?code=...
// 2. Your application's SERVER, holding the application's own certificate:
const appAdapter = nodeHttpsAdapter({ cert: appCertPEM, key: appKeyPEM, ca: caPEM });
const tok = await exchangeCode(appAdapter, "https://authbox.example/oidc/v1/token", {
code, redirectUri: "https://my-app.example/callback", codeVerifier: pkce.verifier,
});
// 3. Validate the id_token against the deployment's JWKS (an anonymous GET).
const jwksDoc = await (await fetch("https://authbox.example/oidc/v1/jwks")).json();
const keys = await importJWKS(jwksDoc);
const claims = await validateIdToken(tok.idToken, {
keys, issuer: "https://authbox.example", audience: "CN=my-app,O=acme,C=US", nonce,
});
// claims.sub is the logged-in user's DN. An id_token carries identity, plus whatever
// identity FACTS this client's own registration declares (org, ou, email — empty by
// default); no group, mission or clearance claim can exist on one, because a claim
// outlives the request. Those come from /userinfo, evaluated live.
// 4. /userinfo, with the SAME certificate that redeemed the code:
const appTransport = new Transport({ baseUrl: "https://authbox.example", adapter: appAdapter });
const userClaims = await userinfo(appTransport, tok.accessToken);
The raw-fetch equivalence promise
Every call this library makes beyond typing is documented with its raw equivalent in sdk/authboxjs/README.md — redeeming a code, verifying a receipt, submitting an enrolment, reading /me. That is not incidental: the server does not, and will not, grow an operation only this client can reach. If you would rather write the fetch or https.request call yourself against the documented contract, you lose nothing but the typing — every path, header, and status code this library uses is exactly what the served OpenAPI documents (or, for the two hand-written contracts — enrolment and OIDC's browser endpoints — the plain listener contract) already promise you.
What this library will not do for you
- No browser keygen. WebCrypto keys cannot become browser mTLS identities. Make your key and CSR with
opensslorauthboxclient/authboxctl— see Enrolling with AuthBox. - No invented error types. A non-2xx response throws
AuthBoxHttpErrorwith the raw status and body text, and nothing else — this system's refusals explain themselves in the audit record, not in a response an SDK could parse into something more specific than the server actually said. - No XPE delegation assertion. That is scoped-credential, audited territory (
authboxclient/authboxproxy), not something a browser-shaped library offers as a convenience.
For the complete surface — errors, Node/mTLS, enrolment submission, /me — see sdk/authboxjs/README.md in the repository, which this page deliberately does not repeat in full.