@bsv/auth-express-middleware

中文文档
2.3.0发布于 2026/10/28SEE LICENSE IN LICENSE.txtTypeScriptESM + CJS构建来源已验证近 30 天下载 2万

BSV Blockchain mutual-authentication express middleware

原始使用文档
文档目录15 节
  1. Requirements
  2. Install
  3. Basic use
  4. Options
  5. Final authenticated response admission
  6. Horizontally scaled services
  7. Certificates
  8. Public services, CORS, and CSP
  9. Error behavior
  10. Security notes
  11. API
  12. Development
  13. Specifications
  14. License
  15. SDK4 hotfix peer requirement

@bsv/auth-express-middleware

Express middleware for BRC-103 peer-to-peer mutual authentication over the BRC-104 HTTP transport. It handles the public handshake endpoint, verifies authenticated application requests, signs responses, and optionally exchanges verifiable certificates.

The unpublished 2.3.0 candidate adds optional final response admission after BRC-104 signing. Existing routes retain their transport behavior, peer ranges and wire format. Routes needing a durable send fence explicitly install guardAuthenticatedResponse; no consumer migration is required for ordinary responses. The candidate also retains the development-only rate limiter used by bounded local HTTP integration fixtures.

The current release preserves BRC-100 byte fields across supported JSON and byte-array forms, snapshots handshake messages before asynchronous work, and rejects parsed bodies that cannot be represented without losing semantics. Version 2.2.6 also preserves bodyless authenticated requests on Express 4, whose JSON parser can supply an empty-object placeholder where Express 5 supplies undefined. HTTP message framing distinguishes that placeholder from a real JSON {} body, which remains signed data. Existing clients need no changes. Version 2.2.7 also preserves Express's one-argument res.set({ ...headers }) overload, including payment challenge headers. The wrapper forwards the original argument count; Express retains its own header validation and coercion, and signed responses retain their existing BRC-104 representation.

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.

Requirements

  • Node.js 22 or newer
  • Express 4.18 or newer, including Express 5
  • A BRC-100 WalletInterface

The package ships native ESM and CommonJS entry points with declarations for both module systems.

Install

npm install @bsv/auth-express-middleware @bsv/sdk express

Express is a peer dependency, so the middleware uses the application's single Express runtime and type graph. This prevents duplicate Express installations from making AuthRequest or the returned middleware incompatible with the application's route types.

Basic use

Parse the request body before authentication so the signed payload contains the same value your route receives:

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. Requests without BRC-103/104 authentication receive 401. With allowUnauthenticated: true, they continue with req.auth.identityKey === 'unknown'.

The exact /.well-known/auth path is always reachable through this middleware because it establishes the session used by protected routes. Similar prefixes such as /.well-known/auth/extra are not treated as handshake traffic.

Options

const auth = createAuthMiddleware({
  wallet,
  allowUnauthenticated: false,
  sessionManager,
  certificatesToRequest,
  onCertificatesReceived,
  certificateApprovalStore,
  logger,
  logLevel: 'error',
  transportLimits: {
    requestTimeoutMs: 30_000,
    maxPendingRequests: 1_000,
    maxRequestBytes: 8 * 1024 * 1024,
    maxResponseBytes: 8 * 1024 * 1024
  }
})
  • wallet is required and must implement the BRC-100 wallet interface.
  • allowUnauthenticated defaults to false.
  • sessionManager accepts the SDK's SessionManager or an AsyncSessionManager.
  • certificatesToRequest asks a peer for allowed certificate types and fields. The legacy v0.1 shape does not assert that every listed type or field was supplied; inspect the validated certificates in onCertificatesReceived before approving.
  • onCertificatesReceived may be synchronous or asynchronous. It receives (senderPublicKey, certificates, req, res, approve). When this callback is configured, the protected request remains blocked unless the callback explicitly calls approve(). Returning normally without approval is a denial and eventually produces the configured authentication timeout. Calling approve more than once has no effect. The callback must validate every application-specific certificate policy, including required type and field completeness, before approving.
  • certificateApprovalStore records that approval against the exact BRC-103 session nonce and identity. The default InMemoryCertificateApprovalStore is bounded to 10,000 approvals and is appropriate only when the handshake and protected request remain in one process. A replicated service using onCertificatesReceived must inject a shared store and retain approvals for at least the corresponding session lifetime. Store failures and every verdict other than exact boolean true fail closed.
  • logger and logLevel enable structured lifecycle logs. Authentication headers, certificate bodies, signatures, response bodies, and wallet objects are not logged.
  • transportLimits.requestTimeoutMs bounds handshake, verification, certificate, and response-signing state. It defaults to 30 seconds.
  • transportLimits.maxPendingRequests bounds per-process pending protocol state. It defaults to 1,000 and fails closed with 503 at capacity.
  • transportLimits.maxRequestBytes bounds handshake plain-data and encoded request-body work before peer processing, excluding received HTTP headers. It defaults to 8 MiB. Set it to -1 only when the embedding service enforces an equivalent request budget.
  • transportLimits.maxResponseBytes bounds application responses buffered for BRC-104 signing, including files passed to res.sendFile. It defaults to 8 MiB and fails closed with a signed 413 response. Set it to -1 only when the embedding service enforces an equivalent response budget.

Invalid option types fail during startup.

Final authenticated response admission

guardAuthenticatedResponse(res, guard) is an opt-in boundary for services that must recheck authorization or output eligibility after asynchronous response signing. Install it in an authenticated handler before sending the response. It rejects unauthenticated requests, repeated registration and late registration.

The guard receives an owned candidate containing the authenticated caller, BRC-104 transport request ID, attempt number, status, response headers and body bytes. Logical application operation IDs remain separate. Headers include the BRC-104 authentication metadata and normalized content length; Node may add its ordinary Date/connection metadata. Signature coverage remains BRC-104's existing status/body and selected-header rules; ordinary CORS/cache headers do not become cryptographically signed merely because they appear in this snapshot. It may await acquisition of the application's durable send fence. While holding that fence, recheck current authorization and every disclosed target, then call enqueue() synchronously. The callback queues the already signed bytes through Node's native HTTP response methods. It never signs, waits for a database, or authorizes the caller itself. A handler-level lock around res.send() is insufficient because signing happens later. The application's writers must use the paired fence, including writers in other processes.

If the candidate became stale, return a complete replacement with statusCode, headers and body: Uint8Array without calling enqueue(). The middleware discards the first signed candidate and signs the replacement for the same request and session. The guard runs again with attempt: 1; it must recheck current access and enqueue this response or fail closed. Only one replacement is allowed. Its body plus UTF-8 header names and values may total at most 64 KiB, and its body must also fit the configured response-body limit. Include required CORS, service capability and error-profile headers explicitly: discarded candidate headers are not inherited. Never copy confidential candidate headers into a public error.

Both signing attempts and guard acquisition share the configured response timeout. Disconnect or timeout aborts the supplied signal, closes the response and invalidates retained enqueue callbacks. A guard must honor cancellation and release its own acquired resources. Calling enqueue twice, calling it after the guard returns, returning without a decision, or failing after bytes were queued cannot append an unsigned fallback. Failure may close the connection without an HTTP error; clients should recover through their durable operation protocol.

This mode supports native Node HTTP/1 responses, including HTTPS, with no compression, deferred encoding, or replacement response/header methods before or after authentication. Unsupported composition is rejected. Configure the route outside such middleware and qualify the complete transport stack. content-encoding may be absent or identity; transfer framing and exact content length belong to the native transport. Guarded responses send their explicit bytes and status without Express's automatic ETag, conditional-response or body transformations. HEAD, 204, 205 and 304 responses must already have empty signed bodies. In particular, HTTP 205 Reset Content prohibits response content; the guard rejects it before signing rather than letting a client discard signed bytes. Ordinary routes keep their existing Express behavior.

The guard provides the transport boundary, not a root-eviction policy, shared database, access policy, or index projection adapter. Those components must be installed and tested together. See the root coordination guide and the compiled interface example.

Horizontally scaled services

The default SessionManager is process-local. Use a shared AsyncSessionManager when a load balancer can route the handshake and the authenticated request to different instances:

import type { AsyncSessionManager } from '@bsv/sdk'

const sessionManager: AsyncSessionManager = {
  async addSession(session) {
    await sessions.put(session.sessionNonce, session)
  },
  async updateSession(session) {
    await sessions.put(session.sessionNonce, session)
  },
  async getSession(identifier) {
    return await sessions.get(identifier)
  },
  async removeSession(session) {
    await sessions.delete(session.sessionNonce)
  },
  async hasSession(identifier) {
    return (await sessions.get(identifier)) !== undefined
  }
}

app.use(createAuthMiddleware({ wallet, sessionManager }))

The backing store must preserve the SDK's session semantics and should use appropriate atomicity, expiry, availability, and encryption controls. If onCertificatesReceived is configured, the same replicas must also share certificateApprovalStore; approval is keyed by exact session nonce and identity, not merely by identity. Sticky routing is not a substitute for shared state when instances can be replaced.

Certificates

app.use(
  createAuthMiddleware({
    wallet,
    certificatesToRequest: {
      certifiers: ['<compressed-certifier-public-key>'],
      types: {
        '<base64-certificate-type>': ['firstName']
      }
    },
    async onCertificatesReceived(senderPublicKey, certificates, req, res, approve) {
      await authorizeDisclosedFields(senderPublicKey, certificates)
      approve()
    }
  })
)

The application remains responsible for authorization policy, certificate revocation checks, and safe storage of disclosed data. A missing required certificate fails with a stable public error. Internal wallet, signing, and certificate-handler errors are logged only through the optional logger and are not returned to callers.

Authenticated res.sendFile() responses preserve Express's root, dotfiles, start, end, and headers security-relevant options while the file is buffered for signing. Relative paths require root; traversal and symlink escapes outside that root fail closed. Other send-file cache and range negotiation options remain the application's responsibility.

The response wrapper also buffers write(), writeHead(), flushHeaders(), and data passed to end() so those standard Node/Express paths cannot escape the signed response. Direct status and header state is captured before signing. Streaming remains bounded buffering: this protocol must know the complete body before it can sign it.

Public services, CORS, and CSP

This package does not impose CORS, CSP, or an origin allowlist. That is intentional: auth endpoints may serve browser apps, WUI, mobile clients, and other callers across many domains. Configure those policies at the application or edge layer:

  • Keep public-service access available by default when that is the service contract.
  • Offer an operator-configured origin allowlist as an opt-in restriction.
  • Never combine Access-Control-Allow-Origin: * with credentialed CORS.
  • Expose the required x-bsv-auth-* response headers to browser clients.
  • Handle OPTIONS before authentication when browser preflight is supported.
  • Treat CSP as a browser-document policy; API responses generally need CORS and transport controls instead.

Do not hard-code a deployment-specific domain list in this middleware.

Error behavior

Public errors are deliberately stable and do not include internal exception messages:

StatusCodeMeaning
400ERR_AUTH_MALFORMEDInvalid handshake or auth headers
400ERR_CERTIFICATES_REQUIREDRequired certificates were not supplied
401UNAUTHORIZED / ERR_AUTH_FAILEDAuthentication was absent, or nonce, signature, session, or HMAC verification failed
408ERR_AUTH_TIMEOUTA bounded protocol step timed out
500ERR_INTERNAL_SERVER_ERRORInternal auth processing failed
500ERR_RESPONSE_SIGNING_FAILEDThe authenticated response could not be signed
503ERR_AUTH_CAPACITYPending-auth state reached its configured limit

Security notes

  • Use HTTPS. Mutual authentication provides integrity and identity, not confidentiality for all HTTP metadata and content.
  • Install the middleware once per request path; response methods are temporarily wrapped while an authenticated response is signed.
  • Do not trust req.auth.identityKey === 'unknown' as authorization.
  • Use shared session state for multi-instance deployments.
  • Keep timeouts, response sizes, and capacity limits finite and monitor 408/413/503 rates.
  • Validate authorization separately after identity authentication.
  • BRC-104 v0.1 signs the method, pathname, query, selected headers, and body; it does not sign the scheme/authority (Host), Cookie, forwarding headers, or arbitrary standard headers. This is deliberate because browser and webpage libraries often cannot safely observe those values when signing. Never select a tenant or grant authority from those omitted values. Pin the expected authority at the edge and compare any security-relevant value with an exact signed x-bsv-* or Authorization field. Use distinct server identity keys when virtual authorities are separate security principals. A valid signature authenticates only this documented subset, not the complete browser or proxy context.
  • Signed response headers are likewise limited to x-bsv-* (excluding the auth envelope) and Authorization. Do not carry authenticated decisions in unsigned Location, cookie, content-type, or other response metadata.
  • Parse JSON, URL-encoded, text, and binary bodies with their matching Express parser before auth. URL-encoded parsed objects must contain only exact string fields; nested/array coercions and unsupported nonempty bodies fail closed.
  • Keep request body limits and normal Express hardening in place.
  • Late authentication failures after a response or connection has already settled are contained to that request and are never written as a second Express response.

API

Runtime exports:

  • createAuthMiddleware
  • ExpressTransport

Type exports:

  • AuthMiddlewareOptions
  • AuthRequest
  • AuthTransportLimits
  • CertificateApprovalStore

Runtime approval-store export:

  • InMemoryCertificateApprovalStore
  • LogLevel

See API.md for generated signatures.

Development

pnpm typecheck
pnpm lint
pnpm format:check
pnpm test:coverage
pnpm pack:check

pack:check builds and validates the exact npm tarball in ESM and CommonJS consumer probes, plus real authenticated requests on Express 4 and 5 with legacy and current SDK clients. Tests do not rebuild the package as a side effect.

Specifications

License

Current TS Stack changes are licensed under the Open BSV License Version 6; see LICENSE.txt. This package also retains pre-uniformization code under the Open BSV License Version 4. Redistributors must preserve THIRD_PARTY_NOTICES.md and the applicable text in LICENSES/.

Guarded responses and replacements own their byte snapshots, including Node Buffer inputs. Subsequent caller mutation does not change the bytes selected for signing or enqueue. Durable guard callbacks still must check current application authorization and enqueue synchronously; owned bytes do not supply that authority.

SDK4 hotfix peer requirement

The SDK4 peer alternative starts at @bsv/sdk ^4.0.1, which restores numeric wallet results and legacy wire compatibility. Existing SDK2/3 alternatives and lower bounds remain unchanged. SDK4 users should install the 4.0.1 hotfix; no application-wallet wire or data migration is required.

版本与发布记录

@bsv/auth-express-middleware 共发布 45 个版本,首次发布于 2025/01/07;过去 12 个月发布了 23 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.3.0latest2026/10/2811 天前
2.2.102026/10/2415 天前
2.2.92026/10/031 个月前
2.2.82026/09/251 个月前
2.2.72026/09/241 个月前
2.2.62026/09/241 个月前

其他发布通道:next → 2.0.0-beta.0。

关于 @bsv/auth-express-middleware 的常见问题

@bsv/auth-express-middleware 是什么?

BSV Blockchain mutual-authentication express middleware

如何安装 @bsv/auth-express-middleware?

在项目目录运行 npm install @bsv/auth-express-middleware,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:express(>=4.18.0 <6)、@bsv/sdk(^2.8.5 || ^3.0.0 || ^4.0.1)、@types/express(>=4.17.0 <6)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bsv/auth-express-middleware 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bsv/auth-express-middleware 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@bsv/auth-express-middleware 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bsv/auth-express-middleware 可以商用吗?

@bsv/auth-express-middleware 使用 SEE LICENSE IN LICENSE.txt 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@bsv/auth-express-middleware 的下载量有多少?

根据 npm 官方下载统计,@bsv/auth-express-middleware 在 2026/10/09 – 2026/11/07 这 30 天内被下载 19,797 次,日均约 660 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bsv/auth-express-middleware 还在维护吗?

最新版本 2.3.0 发布于 2026/10/28(11 天前);至今共发布 45 个版本,其中过去 12 个月发布了 23 个;首次发布于 2025/01/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。