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) #

stable0.4.9

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) #

stable0.4.9hipaapci-dssgdprsoc2
{
  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.