Keyring

Express

One middleware for verification and limits, a second one after your body parser for idempotency.

Every block on this page is a region of a file that the docs' test suite runs against the real @keyring/express before the page is published.

Minimal

import express from 'express';
import { keyring } from '@keyring/express';

const liveOrders = [{ id: 'ord_live_1', total: 4200 }];
const testOrders = [{ id: 'ord_test_1', total: 100 }];

const app = express();
app.use(
  keyring({ resources: { orders: { live: liveOrders, test: testOrders } } }),
);

app.get('/v1/orders', (req, res) =>
  res.json({
    tenant: req.keyring?.tenantId,
    env: req.keyring?.env,
    degraded: req.keyring?.degraded,
    orders: req.keyring?.orders,
  }),
);

app.listen(Number(process.env.PORT ?? 3000), '127.0.0.1', () => {
  process.stdout.write('listening\n');
});

keyring() reads KEYRING_SECRET_KEY, KEYRING_PROJECT_ID and KEYRING_BASE_URL from the environment. req.keyring is KeyringContext | null: null on a route configured skip: true, and absent entirely on anything mounted above the middleware, so read it with ?..

Where to mount it

  • Before your routes and before any body parser. Verification needs only the Authorization header, and an unauthenticated caller must not get your JSON parser to run on a 5 MB payload.
  • After express.static, or the middleware verifies your favicon.
  • Beside, not instead of, your own authentication. Keyring answers "which tenant of yours is calling, in which environment", not "which of your staff is calling".
  • Your error handler stays last. The middleware either calls next() or writes its own 401, 403 or 503; it never throws into yours.

Per-route rules and idempotency

const mw = keyring({
  resources: { orders: { live: liveOrders, test: testOrders } },
  routes: {
    'GET /healthz': { skip: true },
    'POST /v1/payments': {
      onUnavailable: 'closed',
      scopes: ['write:payments'],
    },
    '/v1/internal/*': { onUnavailable: 'stale-then-closed' },
  },
  onError: (error) => console.error('keyring', error),
});
app.use(mw);
app.use(express.json());
app.use(keyringIdempotency(mw.keyring));

A rule is keyed by METHOD /path or by a path pattern with *. skip leaves req.keyring null and records no event; onUnavailable and scopes are the modes and the scope check. keyring.stats().unmatchedRoutes names a rule that never matched, which is what catches a typo that would leave a money route on the fail-open default.

Idempotency is a second mount after your body parser because the fingerprint hashes the body. The docs' test suite drives this exact file: a replayed POST /v1/payments with the same Idempotency-Key answers the stored 201 with idempotent-replayed: true, the same key with a different body answers 422, and a key without write:payments answers 403 with missing_scopes: ['write:payments'].

onError

The SDK's one operator channel. A failed poll, a failed disk persist, a project that outgrew the cache: each arrives here as an Error. Log it. On a healthy deployment it is quiet.

On this page