Browser Fingerprinting: Introducing My First NPM Package

Published: September 8, 2023

This post is to share my debut into the world of NPM packages; a zero dependencies package exporting a swift and synchronous function to compute a browser fingerprint. No user permissions or cookie storage is needed either! Dive into the details on NPM or check out the source code on Github.

Device Fingerprinting

In the age of the internet, accurately identifying visitors is paramount for websites, whether it's to serve personalized content or keep malicious actors at bay. Although cookies have been the traditional heroes, their limitations, like incompatibility with incognito modes and multi-browser usage, led to the invention of device fingerprinting.

Often referred to as machine fingerprint, browser fingerprint, and several other names, a device fingerprint collects specific information about an online device, ensuring its unique identification during future visits. This form of identification remains robust, even when users disable cookies or other tracking tools.

The essence of browser fingerprinting lies in capturing a unique combination of a user’s web browser and device details, creating a distinctive 'digital fingerprint'. For example, while many visitors to a website may have the same model of iPhone, the software and drivers installed, geo-location, languages, browser and OS version, and even minute variances in the hardware could be different. Browser fingerprinting techniques gather one or more of these signals and aim to capture these minor variances between users. Such fingerprints are resilient, remaining consistent across incognito sessions or when users are VPNs.

The Data Behind Browser Fingerprinting

The depth of information that browser fingerprinting can mine is truly staggering. From device models, operating system versions, and browser specifications to intricate details like user timezone, preferred language settings, and ad blocker usage, the scope is vast. The function used in my package amalgamates many of these signals. For example, below is a screenshot of some of the data I decided to use in my NPM package to generate a fingerprint:

Browser Fingerprint

For a closer look, explore the deployed Next app here

⚠️

Be careful: The strongest discriminating factor is canvas token which can't be computed on old devices (e.g. iPhone 6)

This data is then run through a MurmurHash3 hashing algorithm to generate a unique fingerprint for each browser.

Singleton Pattern: Efficient and Effective

For the creation of the fingerprint, I've opted for the singleton design pattern. This ensures that the class is instantiated only once. On successive calls to the fingerprint function, the very same instance returns, facilitating faster caching of fingerprint data and swifter function calls. This approach has proven effective, especially when paired with API calls to authenticate that the user making a request was the same user who logged in and was issued the JWT.

A Quick Tip on Hydration Errors

To avoid the hydration errors we get when using checks like typeof window !== 'undefined' in the logic, use the following approach: Import the fingerprintBrowser function from the package:

import { getBrowserFingerprint } from "fingerprint-browser";

Next, make use of it within a useEffect hook:

  const [browserFingerprint, setBrowserFingerprint] = useState("");
  useEffect(() => {
    setBrowserFingerprint(fingerprintBrowser());
  }, []);

This approach ensures that server-side rendering remains unaffected as hooks aren’t executed. Wrapping the window usage inside a useEffect triggered on mount ensures the client executes it post hydration.

Conclusion

To conclude, browser fingerprinting is an innovative, resilient, and essential tool for the modern web. I'm happy to have contributed to this domain, and I invite developers and enthusiasts to explore and provide feedback on my package :)


References and Special Thanks:

  1. Thanks to @damianobarbati for get-browser-fingerprint which provided some inspiration
  2. Special thanks to Valentin Vasilyev for the original fingerprintjs slightly modified
  3. Thanks to Open Source Device Fingerprinting by Dark Wave Tech for the various identity functions
  4. Dave Alger for his fingerprinting CodePen
  5. N8Brooks on Github for his implementation of unsigned 32-bit MurmurHash3