fetch-message-signatures
v0.1.0
Published
HTTP Message Signatures (RFC 9421) for the Fetch API
Maintainers
Readme
fetch-message-signatures
fetch-message-signatures is a JavaScript module for HTTP Message Signatures (RFC 9421) built
on the Fetch API.
The module provides sender, recipient, and Accept-Signature operations on top of Request,
Response, Headers, and fetch, together with Web Cryptography implementations for ECDSA P-256,
ECDSA P-384, Ed25519, RSA-PSS with SHA-512, and RSASSA-PKCS1-v1_5 with SHA-256. HMAC and other
cryptography can be supplied through custom providers. Trusted-key selection and authorization
remain application responsibilities.
RFC 9421 is built on Structured Fields (RFC 9651), and the parser and serializer behind it are exported for HTTP fields this module does not define.
💗 Help the project
Support from the community to continue maintaining and improving this module is welcome. If you find this module useful, please consider supporting this project by becoming a sponsor.
Dependencies: 0
fetch-message-signatures has no dependencies and it exports tree-shakeable ESM from a single
module.
API Reference
fetch-message-signatures is distributed via
npmjs.com,
jsdelivr.com, and
github.com.
Quick Start
Sign every outgoing request and send it. createSigningFetch() returns a drop-in fetch, so the
signing happens around the call you already make.
import * as FetchSig from 'fetch-message-signatures'
// 1. Your signing key
const { privateKey } = await FetchSig.generateEd25519KeyPair()
// 2. Wrap fetch
const signingFetch = FetchSig.createSigningFetch({
sign: {
signer: FetchSig.ed25519Signer(privateKey),
components: ['@method', '@authority', '@path'],
parameters: { alg: 'ed25519', keyid: 'client-key' },
},
})
// 3. Send it, exactly like fetch. The request goes out signed.
const response = await signingFetch('https://api.example/orders/123')
const order = await response.json()The request that went out carries the two fields RFC 9421 defines:
signature-input: sig1=("@method" "@authority" "@path");created=1735689600;alg="ed25519";keyid="client-key"
signature: sig1=:<base64 of the 64 Ed25519 signature bytes>:The recipient checks that signature against explicit policy. A valid signature on its own is not enough: the components it covers and the parameters it carries have to be the ones the application requires, which is why the policy is spelled out rather than defaulted.
declare const incoming: Request
declare const clientPublicKey: CryptoKey
const verified = await FetchSig.verify(incoming, {
verifier: FetchSig.ed25519Verifier(clientPublicKey),
policy: {
requiredComponents: ['@method', '@authority', '@path'],
requiredParameters: ['created', 'keyid'],
algorithms: ['ed25519'],
maxAge: 60,
},
})
// sig1 ed25519
console.log(verified.label, verified.algorithm)A recipient that accepts more than one client selects the trusted key itself, from the signature's
keyid. That is what VerifierFactory is for, and the
recipient guide covers it.
Signing requests is the common direction, because it only asks the recipient to verify. When the
server signs its responses too, createSignedFetch() adds
verification on the way back in, and
createVerifyingFetch() does that alone so a bundler can
omit the signing side. To sign a message you already hold, rather than wrapping fetch, use
sign().
The bytes that were signed are one canonicalized line per covered component plus the
@signature-params line, which repeats the Signature-Input member value. createSignatureBase()
returns exactly that string, which is the first thing to compare when two implementations disagree:
"@method": GET
"@authority": api.example
"@path": /orders/123
"@signature-params": ("@method" "@authority" "@path");created=1735689600;alg="ed25519";keyid="client-key"Guides
For sender and recipient integration, cryptographic providers, component selection, Structured
Fields, Accept-Signature, Fetch behavior, and security guidance, see the
guides directory.
Runtime Requirements
The Fetch wrappers, sign(), appendSignature(), signRequested(), and appendAcceptSignature()
require standards-compatible Request, Response, and Headers implementations, and the wrappers
also require fetch itself. Every reading operation, including createSignature() and verify(),
additionally accepts a plain object carrying method, url, and headers for a request, or
status and headers for a response, so a server that never constructs a Request can sign and
verify by attaching the returned field values itself. A descriptor field value can be an array of
occurrences in wire order, and an optional trailers record supplies trailer occurrences. The
built-in cryptographic providers require the Web Cryptography API and runtime support for the
selected algorithm. The package does not provide polyfills.
Signing and verification capture the target message and related request into package-owned,
immutable snapshots. A verifier factory and policy.validate receive the same MessageSnapshot,
whose lowercase header and trailer names map to frozen occurrence arrays, rather than the caller's
mutable message object. Keep the source message stable until the operation settles: it is compared
with the snapshot after application callbacks run, and a change rejects the operation.
Package configuration records must be object literals or null-prototype objects whose own members
are enumerable data properties. Frozen records are supported; class instances, inherited or
non-enumerable configuration, accessors, and Proxies are not. Fetch-wrapper RequestInit values
follow the same rule. This restriction does not apply to message and header inputs, cryptographic
provider implementations, or host objects such as CryptoKey, Request, Response, and Headers.
Structured Field Byte Sequences use Uint8Array.prototype.toBase64() and Uint8Array.fromBase64()
where they are available, and fall back to btoa() and atob() where they are not. Both paths
produce the same results and are covered by the test suite.
Runtime-specific behavior that affects signatures, such as manual redirect handling in browsers, repeated field lines, trailers, and response reconstruction, is documented in Fetch behavior and limitations.
Supported Operations
- Sign requests and responses.
- Verify one or more message signatures against explicit recipient policy.
- Generate key pairs containing Web Cryptography's
CryptoKeyobjects and providers for ECDSA P-256, ECDSA P-384, Ed25519, RSA-PSS with SHA-512, and RSASSA-PKCS1-v1_5 with SHA-256. - Bind a response signature to components of its related request.
- Create, parse, append, and fulfill
Accept-Signaturerequests. - Derive RFC 9421 request and response components from Fetch messages or plain descriptors.
- Process HTTP fields as Structured Fields, dictionary members, raw byte sequences, or trailers.
- Parse and serialize Structured Field Dictionaries, Lists, and Items.
- Wrap
fetchwith request signing, response verification, or both through independent tree-shakeable exports.
Cryptographic Algorithms
fetch-message-signatures includes tree-shakeable key-pair generators, signer factories, and
verifier factories backed by Web Cryptography for ecdsa-p256-sha256, ecdsa-p384-sha384,
ed25519, rsa-pss-sha512, and rsa-v1_5-sha256. Other algorithm identifiers and key systems,
including hmac-sha256, can be supplied through the Signer and
Verifier interfaces. Applications choose trusted keys, algorithms,
and authorization policy.
The cryptographic providers guide documents the exact algorithm mappings, key extractability, signature parameter handling, and custom provider contract. Exported functions are listed under Cryptographic Algorithms in the API reference.
Security Considerations
A valid signature authenticates only its covered components. It does not by itself establish
authorization, freshness, replay protection, or body integrity. Applications must define trusted
keys, required component coverage, timestamp and nonce policy, and independently validate
Content-Digest when body integrity matters.
Fetch also hides or normalizes some protocol-layer HTTP details. Read the security guidance and Fetch behavior before deploying signatures across a network boundary. Security vulnerabilities should be reported according to the Security Policy.
