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.
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,
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.
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.
408, 413, and
503.Runtime:
createAuthMiddlewareExpressTransportTypes:
AuthMiddlewareOptionsAuthRequestAuthTransportLimitsLogLevel@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