paritytech / paritytech/web3-storage
Provider HTTP auth scheme: investigate, capture in design, harden binding, and extract a single source of truth
@danielbui12 is already working on this.
Since Jul 21, 2026.
- Dominant language
- Rust
- Stars
- 12
- Forks
- 3
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 33
Description
Context
PR #245 removed --disable-auth and made the signed-Authorization scheme the provider's only request-auth path. The scheme now works and is aligned with the (thin) design note, but it grew organically across several crates and is under-specified. This issue tracks the investigation + design adjustment + refactor to put it on a solid footing. It is not a "docs typo" task — it needs a design decision and a code extraction.
Where the scheme lives today (scattered — needs one source of truth)
provider/negotiation/src/http_auth.rs—auth_message/build_auth_header(the Rust wire format). Its placement here was already flagged as unsettled in review ("feels a bit weird place", https://github.com/paritytech/web3-storage/pull/245#discussion_r3602031643).provider-node/src/auth.rs—verify_signature,wrap_bytes,MembershipCache,require_role.packages/core/src/http.ts—signProviderRequest, which re-hardcodes the message stringweb3storage:${method}:${bucketId}:${timestamp}independently of the Rust definition.client/src/storage_user.rs— Rust client signing.docs/design/scalable-web3-storage-implementation.md§"Authentication & RBAC" — documents the raw form only.
The wire format is effectively defined in two places (Rust provider-negotiation + TS core) kept in sync by hand. Extract a single canonical definition both the provider verifier and all clients reference, and settle its home.
Work items
1. Capture the scheme in the design. Currently §Auth & RBAC documents only the raw signature over web3storage:<METHOD>:<bucket_id>:<timestamp>. It omits that the provider now also accepts the <Bytes>-wrapped form that wallets / PAPI signBytes produce (see wrap_bytes in provider-node/src/auth.rs) — which is what every real wallet and the UIs actually send. Document both accepted forms and why (or remove wrapping — see open questions).
2. Adjust the design to close the binding/replay gap (the core reason this needs a design change, not just docs). The signed message binds only method + bucket + timestamp; it binds nothing else:
- No body binding — a captured
PUT /node/POST /commitheader authorizes any body to that bucket for the whole skew window (default 5 min,provider-node/src/cli.rs); on plaintext HTTP an on-path attacker can swap the uploaded bytes. - No provider binding — a header signed for provider A verifies at provider B for the same bucket (cross-provider replay).
- No nonce/uniqueness — plain in-window replay. (The provider's existing
NonceStoreguards on-chain commitment negotiation only; it is not on this HTTP path.)
Raised by @danielbui12 in review (https://github.com/paritytech/web3-storage/pull/245#discussion_r3585550318). Proposed direction: bind blake2_256(body) for mutating endpoints + the provider account/id + a uniqueness token, and shorten the skew. Constraint: the message is what wallets sign via signBytes, so added fields must be computable client-side (body hash + provider id are; a server-issued nonce adds a round-trip and breaks the stateless header — weigh the trade-off). Coordinate with the wallet-signing rework (https://github.com/paritytech/web3-storage/pull/245#discussion_r3549962873 / https://github.com/paritytech/web3-storage/pull/245#discussion_r3613809927).
3. Refactor/extract. One canonical definition of the wire format (message construction + <Bytes> handling + header encode/decode) shared by the provider verifier and every client (Rust + TS), with a settled module home — so the format can't drift between languages.
Open questions
- Do we even need the
<Bytes>wrapping / per-request wallet signatures? Today the UIs still derive a keypair locally (ChainSigner.keypairis optional but present), so they could sign the raw message — the wrapping is only forced once a real wallet extension is wired (extensions never expose the raw key and only sign the<Bytes>…</Bytes>-wrapped form). It was added in #245 to unify all TS signing onsignBytesand to drop the mandatory raw keypair. Decide between: keep wrapping (wallet-ready, but non-spec until documented), revert to raw-only now and defer wallet signing, or move to a session-token / login model (sign once → short-lived bearer token → token per request) — which removes per-request wrapping entirely and fixes the signing-popup-per-request UX flagged in review (https://github.com/paritytech/web3-storage/pull/245#discussion_r3549962873). - Stateless signed header vs. server-issued nonce/token — a server nonce closes replay tightest but adds a round-trip and state; a session token amortizes it. Pick the model before binding more fields.
Acceptance
- Design doc §Auth & RBAC updated to the final (hardened) scheme; signature form(s) documented (or wrapping removed).
- Single canonical auth-format module; TS and Rust no longer hardcode the string independently.
- Replay / body-tamper / cross-provider regression tests once the scheme lands.
Related: PR #245, #94 (HTTP→P2P transport, https://github.com/paritytech/web3-storage/issues/94#issuecomment-4777560110)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.