Request vs. response replay attacksThis guide is designed to prevent replay attacks against your own server endpoints, where the attacker reuses something from the Fingerprint API response (
request_id or visitor_id). To prevent payload replay attacks targeted against the Fingerprint API, see Protecting from payload replay attacks.How NOT to integrate Fingerprint
Imagine you are running a survey on your website and you don’t want to accept more than one submission from a single browser. A simple implementation vulnerable to client-side tampering might look like this:- A person trying to submit hundreds of votes can bypass your website completely and simply spam requests to
yourwebsite.com/api/surveyusing an HTTP library likecURLand supplying a randomvisitorIdwith each request. - Even if you limit your endpoint to only accept requests from your website’s origin, an attacker can use a browser extension to intercept and modify the in-browser request.
Getting the Fingerprint results to your server securely
You have three options to get the Fingerprint results to your server safely:- Server API
- Pass only the
requestIdfrom the JavaScript agent to your server, use it to retrieve the full identification event from the Server API. - The result authenticity is ensured by direct server-to-server communication.
- Pass only the
- Webhooks
- Pass only the
requestIdfrom the JavaScript agent to your server, use it to wait for the corresponding identification event Webhook from Fingerprint. - The result authenticity is ensured by direct server-to-server communication.
- This implementation is the least common. It is usually more complex and has higher latency than Server API or Sealed results. Webhooks are typically used for event storage.
- Pass only the
- Sealed results
- Receive the full identification event in the JavaScript agent as an encrypted Sealed result. Send it encrypted to your server and decrypt it there using a secret key generated using the Fingerprint Dashboard.
- The result authenticity is ensured by symmetric encryption.
- This implementation is faster than Webhooks and Server API (it requires one less HTTP request). It is currently available for the JavaScript agent (no mobile support yet).
/events endpoint to safely retrieve the full identification result. For the other alternatives, see Webhooks or Sealed results.
Related: Hiding the Visitor ID from the client completelyHiding the Visitor ID from the client payload makes it harder for malicious actors to reverse-engineer your implementation. You have two options available:
- Using Sealed results to encrypt the payload returned to the JavaScript agent completely.
- Using Zero Trust Mode to omit the visitor ID and other metadata from the JavaScript agent payload, and only send the
requestIdto the client. Zero Trust Mode requires the Enterprise plan.
Preventing replay attacks
During a replay attack, an attacker intercepts and retransmits data previously exchanged between Fingerprint and your server.- When using Webhooks, you can verify that a Webhook request came from Fingerprint using Basic authentication or Webhook signatures. This prevents replay attacks.
- When using Sealed results, an attacker can intercept the sealed result your client sends to your server.
- When using the Server API, an attacker can intercept previously issued request IDs or generate their own using your Public API key.
- a) Save all request IDs received from the client (sealed or not) or webhooks into a database. When a new request ID comes in that is already present in the database, reject the request.
- b) Check the freshness of the retrieved identification event and its consistency with the HTTP request itself.
A) Saving all request IDs to a database
Here is a simplified example of saving the received request ID into a database and processing only previously unseen request IDs.Server
Note: If you are using Sealed results, it’s a good idea to pass the unencrypted request ID alongside the sealed result to your server as a fallback. The request ID decrypted from the sealed result must the match the one sent without encryption. See Sealed results for more details.
B) Checking the freshness and consistency of the identification result
The code snippets below are simplified for readability, but you can find the full example as used in our use case demos on GitHub. These are common good practices that you might need to adjust according to your specific use case.1. Check the timestamp
If the identification request associated with the survey submission is too old, reject the request. Pick a time limit suitable for your infrastructure setup and use case.validateFingerprintResult.js
2. Check the origin
Both the Fingerprint identification request and the survey submission request should be coming from your website.validateFingerprintResult.js
3. Check the IP address
The Fingerprint identification request and the survey submission request should be coming from the same IP address.validateFingerprintResult.js
4. Optional: Check the confidence score
A confidence score indicates how confident Fingerprint is that the browser (or device) in the request has not been misidentified for another existing browser (or device). You can use it in your validation logic to only allow users identified above a certain threshold of confidence to perform a sensitive action. See Understanding your confidence score for more details.validateFingerprintResult.js
Using Smart signals
ThevalidateFingerprintResult method is a good place to use all the Smart Signals Fingerprint offers for detecting suspicious browser and device configurations.
For example, you can prevent bots, VPN users, Tor browser users, and users tampering with their browser configurations from submitting survey responses:
validateFingerprintResult.js
What’s next
- Have a look at our demo collection to see how the patterns in this guide are implemented for specific use cases. The source code is open-source and available on GitHub.
- If you have more questions about protecting your implementation from tampering and replay attacks, please reach out to our support team.