This source candidate declares SDK peer ^2.8.5 || ^3.0.0. SDK3 remains
a coordinated proposal; see the qualification and migration limits
before adopting it.
Express transport for BRC-103 peer-to-peer mutual authentication over BRC-104 HTTP. It handles the public handshake, verifies authenticated application requests, signs responses, and optionally exchanges verifiable certificates.
npm install @bsv/auth-express-middleware @bsv/sdk expressNode.js 22 or newer and Express 4.18 or newer are required. The package uses the application's peer-provided Express runtime and type graph and provides native ESM and CommonJS entry points with matching declarations.
Version 2.2.4 requires @bsv/sdk 2.7.1 or later so the identity assigned to
req.auth.identityKey is the identity bound to the verified peer session, not
unsigned BRC-104 transport metadata.
Version 2.2.6 restores bodyless authenticated requests when the Express 4 JSON
parser supplies an empty-object placeholder. HTTP message framing distinguishes
that placeholder from a real JSON {} body. Existing clients need no changes;
framed bodies and authentication checks retain their existing semantics.
Version 2.2.7 preserves Express's one-argument response header-map overload.
res.set({ ...headers }) and authenticated payment challenges work with the
same Express validation, signed headers and wire format. No client migration
is required.
Version 2.2.8 requires SDK 2.8.5 and removes middleware header-size and
header-count ceilings. createAuthMiddleware configures its Peer with
maxGeneralPayloadBytes: null, avoiding an indirect SDK general-message
ceiling for received headers while retaining metadata and signature validation.
Received headers are excluded from maxRequestBytes, which still applies to
handshake/plain-data and encoded request bodies. The middleware preserves all
selected header bytes for BRC-104 signing and verification, rather than rejecting
a received payment because of an additional header budget. HTTP server, CDN,
proxy and WAF configuration own transport header limits. Configure those layers
for the largest supported payment proof and validate the complete route.
Malformed or duplicate signed headers, unsafe header values, authentication failures and invalid signatures still fail validation. Body budgets, timeouts and replay protection remain separate. Clients consuming larger signed responses need matching capacity; SDK 2.8.5 raises its header limits by 4x. No BRC100 call, wire or wallet-data migration is required. Source publication is a separate protected release step.
import express from 'express'
import { PrivateKey, ProtoWallet } from '@bsv/sdk'
import { createAuthMiddleware, type AuthRequest } from '@bsv/auth-express-middleware'
const wallet = new ProtoWallet(PrivateKey.fromRandom())
const app = express()
app.use(express.json())
app.use(createAuthMiddleware({ wallet }))
app.get('/private', (req: AuthRequest, res) => {
res.json({ identityKey: req.auth?.identityKey })
})Authentication is required by default. allowUnauthenticated: true permits
requests without auth and marks them with identity key unknown; that value
must never be treated as authorization.
app.use(
createAuthMiddleware({
wallet,
allowUnauthenticated: false,
sessionManager,
certificatesToRequest,
onCertificatesReceived,
certificateApprovalStore,
logger,
logLevel: 'error',
transportLimits: {
requestTimeoutMs: 30_000,
maxPendingRequests: 1_000,
maxResponseBytes: 8 * 1024 * 1024
}
})
)transportLimits bounds pending handshakes, verification listeners,
certificate waits, and response-signing state. maxResponseBytes also bounds
files passed to res.sendFile; oversized application responses fail closed
with a signed 413. The default is 8 MiB. Operators may set it to -1 only
when the embedding service enforces an equivalent response budget. Malformed
requests are rejected before state allocation. At capacity, the middleware
fails closed with 503.
When onCertificatesReceived is configured, approval is retained against the
exact validated session nonce and identity. The default bounded approval store
is process-local. Load-balanced services must inject a shared
certificateApprovalStore as well as a shared AsyncSessionManager; malformed
or unavailable approval-store results fail closed.
The exact /.well-known/auth endpoint remains public because it establishes
the session. Similar path prefixes receive normal auth treatment.
The default SDK SessionManager is process-local. A load-balanced service must
inject a shared AsyncSessionManager so every replica can resolve the same
handshake/session state:
import type { AsyncSessionManager } from '@bsv/sdk'
app.use(
createAuthMiddleware({
wallet,
sessionManager: sharedSessionManager satisfies AsyncSessionManager
})
)Use storage with appropriate atomicity, expiry, availability, and encryption. Sticky routing does not preserve state through process replacement.
app.use(
createAuthMiddleware({
wallet,
certificatesToRequest: {
certifiers: ['<compressed-certifier-public-key>'],
types: { '<base64-certificate-type>': ['firstName'] }
},
async onCertificatesReceived(senderPublicKey, certificates, req, res, next) {
await authorizeDisclosedFields(senderPublicKey, certificates)
next()
}
})
)Applications remain responsible for authorization, revocation checks, and storage of disclosed fields. Callback errors produce generic public responses; certificate bodies and internal errors are not logged by default or returned.
The middleware does not hard-code CORS, CSP, or origin restrictions. Public services can remain accessible across browser apps, WUI, mobile clients, and unknown domains by default, while an operator may opt into a configurable allowlist at the application or edge.
For browsers, handle OPTIONS before auth and expose required
x-bsv-auth-* headers. Do not combine wildcard origins with credentialed
CORS. CSP is primarily a document policy and is not a substitute for API CORS.
Host, Cookie, forwarding headers, or other metadata
omitted by the BRC-104 v0.1 signed frame. This subset is deliberate because
browser and webpage libraries often cannot safely observe those values when
signing. Pin the authority at the edge and compare required values with exact
signed x-bsv-* or Authorization fields. Response authentication likewise
covers only the declared signed header set, not arbitrary standard response
headers or the complete browser/proxy context.408, 413, and
503.Runtime:
createAuthMiddlewareExpressTransportInMemoryCertificateApprovalStoreTypes:
AuthMiddlewareOptionsAuthRequestAuthTransportLimitsCertificateApprovalStoreLogLevel@bsv/payment-express-middleware — authenticated payment gating; runs after
auth@bsv/authsocket / @bsv/authsocket-client — WebSocket mutual auth@bsv/sdk — wallet, peer, session, and AuthFetch implementation