Konvoa Engineering
Replay-resistant authentication for desktop clients
How canonical bytes, timestamps, nonces, body hashes, and device signatures work together to reject captured requests.
TLS is necessary, but replay is a protocol question
TLS protects traffic while it crosses the network. It does not by itself tell an application server whether a valid request was submitted once or copied and submitted again through another valid connection. Logs, local proxies, crash dumps, compromised endpoints, and implementation mistakes can expose otherwise valid request material.
A replay-resistant protocol makes freshness part of the signed message and enforces one-time use on the server. The important pieces are a bounded timestamp, a cryptographically random request identifier or nonce, and durable-enough replay state shared by every instance that may receive the request.
Canonicalize before signing
Both sides must produce the same byte sequence from the same request. Define the order, separators, character encoding, path treatment, and body hashing rules in the protocol specification. Do not sign an in-memory object whose serialization can vary by library, runtime, or property order.
Konvoa's request message uses fixed labels and line order: protocol marker and version, device-key ID, method, exact origin-form path and query, lowercase host, request ID, Unix timestamp, nonce, access-token hash, and exact-body hash. The application identifier remains in the JSON body and is covered by that body hash. Never add, omit, or reorder a line.
AUTHV2-REQ
v=1
kid=jkt_K2...
m=POST
u=/api/v2/desktop/simple/login
host=konvoa.com
rid=7f3d0bb2-3266-4f44-8248-8f0de65f6240
ts=1787716800
nonce=P8...
ath=access_token_sha256_base64url
bh=exact_body_sha256_base64urlValidate freshness before expensive work
Reject malformed field lengths and timestamps outside the accepted skew before password hashing, database joins, or downstream calls. The allowable clock window should match real client conditions and be documented. A wider window helps devices with poor clock synchronization but increases the period during which the replay store must remember a nonce.
Freshness checks are not a substitute for rate limits. Apply independent limits to the network source and the normalized account or device subject. A combined key such as IP plus username lets an attacker rotate usernames to avoid the IP ceiling; separate buckets close that gap.
Consume the nonce atomically
The replay store should perform an atomic insert-if-absent with an expiry at least as long as the accepted timestamp window. Two concurrent copies of the same request must not both observe the nonce as unused. In a distributed deployment, use shared storage with the required atomic primitive rather than a per-process map.
Choose the replay key from authenticated context: application, device-key thumbprint, and nonce are common inputs. Hash the composite before storing it so attacker-controlled identifiers cannot create oversized or ambiguous storage keys. Fail closed when replay storage is unavailable for an operation that requires freshness.
Bind the body and route
A signature over only a nonce and timestamp can be moved to a different route or paired with a different body. Include the method, normalized path, and hash of the exact request bytes. Verify against the raw bytes received, not a JSON object parsed and serialized again.
Apply strict request size limits before buffering. Reject redirects in reference clients so credentials and signatures are never forwarded to a different origin. On the response side, bind the signature to the request identifier so a valid answer from one exchange cannot be substituted for another.
Test the failure cases deliberately
A useful protocol suite sends the identical request twice, changes one body byte without updating the hash, moves a signature to another path, sends timestamps on both sides of the skew boundary, races duplicate nonces, and simulates replay-store failure. Run the same vectors in every supported reference client.
Replay resistance is not one header. It is the combined guarantee that the exact request bytes were signed by the expected device key, were recent enough, and were accepted only once.