API Keys
Long-lived API token primitives — generate / verify / revoke / rotate; sealed at rest; per-key scope + rate-limit. Tokens are Stripe-style prefix-recognizable strings of the form so a leaked credential is identifiable on sight (secret-scanner allowlists, log-grep for bk_live_).
Storage: framework table _blamejs_api_keys with sealed columns (ownerId / scopes / metadata via cryptoField), ownerIdHash for indexed listForOwner. Same dual-storage pattern as sessions — local SQLite in single-node mode, external-db in cluster mode, dispatched via cluster-storage. Hash algorithm is operator- selectable (SHAKE256 default for high-entropy random secrets; Argon2id available for low-entropy deployments). Visibility defaults are ON: auditFailures, auditSuccess, and trackLastUsedAt all default true so HIPAA §164.312(b) / PCI-DSS 10.2.1 / GDPR Art. 32 trails are complete out of the box. Operators with extreme verify-rate volume opt OUT explicitly.
Graceful rotation moves the prior secret hash into a secondarySecretHash slot with a TTL (default 7 days) so in-flight clients survive the rotation window without coordinated redeploy.
b.apiKey.parseFormat(token) #
Pure parser for the framework's token format. Returns { prefix, namespace, idHex, secretHex } on a structurally-valid token, null otherwise. Never touches the registry — used by routing code that wants to dispatch a request to the correct registry (multi-namespace deployments) before calling verify(). Hex parts are not constant-time-compared here; that happens inside verify() against the stored hash.
var parts = b.apiKey.parseFormat("bk_live__");
// → { prefix: "bk", namespace: "live", idHex: "",
// secretHex: "" }
b.apiKey.parseFormat("not-a-token"); // → null
b.apiKey.parseFormat("bk_live_xyz_zzz"); // → null (non-hex)
b.apiKey.create(opts) #
{
namespace: string, // registry namespace (required, no underscores / whitespace)
prefix: string, // token prefix (default "bk", no underscores)
idBytes: number, // bytes of id randomness (default 8 → 16 hex chars)
secretBytes: number, // bytes of secret randomness (default 16 → 32 hex chars)
trackLastUsedAt: boolean, // update lastUsedAt on verify success (default true)
auditFailures: boolean, // emit verify-failure audits (default true)
auditSuccess: boolean, // emit verify/list/get-success audits (default true)
purgeAfterMs: number, // age threshold for purgeExpired (default 90 days)
hashAlgo: string, // "shake256" (default) or "argon2id"
audit: b.audit, // optional audit sink
clock: function, // () → unix ms (test override)
}
Build an API-key registry bound to a single namespace. Returns a handle exposing async issue / verify / revoke / rotate / listForOwner / getById / purgeExpired. State changes (issue / revoke / rotate / purgeExpired) require leader in cluster mode; reads (verify / getById / listForOwner) run on any node. Issued tokens contain the secret material exactly once — the registry persists only the SHAKE256 / Argon2id hash and a scrub-safe record without secrets. Operators with multiple key lifecycles (e.g. live / test) instantiate one registry per namespace.
var keys = b.apiKey.create({
namespace: "live",
audit: b.audit,
});
var issued = await keys.issue({
ownerId: "user-42",
scopes: ["read:users", "write:posts"],
metadata: { name: "Mobile app v3" },
expiresAt: Date.now() + b.constants.TIME.days(90),
});
// issued.key — "bk_live_5b9e7c8a4f2d1e3a_8a7b6c5d4e3f2a1b" (returned ONCE)
var record = await keys.verify(req.headers["x-api-key"]);
if (!record) return res.writeHead(401).end();
// → { id, ownerId, scopes, metadata, lastUsedAt, ... }
// Graceful rotation — old secret keeps working for 7 days:
var rotated = await keys.rotate(issued.id, { graceful: true });
await keys.revoke(issued.id); // immediate cutover
var owned = await keys.listForOwner("user-42");
Last updated 2026-08-08T16:39:49.652Z by seeder.