Account sharing is multi-accounting in reverse: one login spread across many physical devices. Counting distinct cross-browser hardware fingerprints per account measures real machines, so one person on two browsers counts once and a sharer who switches browsers does not look like a new device.
Account sharing is multi-accounting in reverse
Multi-accounting is many accounts behind one device. Account sharing is the mirror image: one account used across many physical devices. A password passed around a friend group, a per-seat license shared beyond its count, a single subscription logged in from three cities. The revenue leak is just as real and harder to see, because every session looks like a legitimate login to a valid account.
The quantity to measure is the number of distinct physical devices a single account is used from. A subscription sold per seat or per household has an implied device boundary; account sharing is the account quietly crossing it.
Why the usual signals mismeasure it
IP address tells you location changed, not whether the device did. A shared account and a travelling user look identical, and a household behind one NAT looks like a single device when it is several.
Cookies and per-browser fingerprints over-count and under-count at the same time. One person who uses Chrome on a laptop and Safari on a phone looks like two devices to a per-browser hash, which inflates the sharing signal and flags a legitimate single user. Meanwhile a sharer who clears storage looks like a brand-new device on every session.
Concurrent-session limits catch only simultaneity. Two people who never stream at the same time can share one login all month without ever tripping a concurrency cap. Sequential sharing is invisible to it.
Count physical devices, not browsers
The measurement that holds is the count of distinct `hardwareFingerprint` values on an account. The hardware fingerprint is derived from hardware-bound signals (GPU identity, CPU architecture, installed fonts, screen geometry, and related device-level facts), so it is identical across Chrome, Safari, Firefox, and Brave on one machine. That property is exactly what makes it the right device counter.
It corrects both error directions at once. One person on two browsers of the same laptop produces one hardware fingerprint, so they count as one device, not two. A sharer who opens a different browser to look new still produces the same hardware fingerprint as their other sessions on that machine, so a browser switch does not inflate the count. You are counting machines, which is what 'how many devices is this account on' actually means.
import { getFingerprint } from 'doorman-benny';
// On each login, record the physical device behind the session.
async function recordDevice(accountId: string) {
const result = await getFingerprint();
await fetch('/api/account/device', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
accountId,
// One stable id per physical device, the same in every browser.
hardwareFingerprint: result.hardwareFingerprint,
fingerprintResult: result, // store for fuzzy de-duplication later
}),
});
}Record the hardwareFingerprint on every login. The set of distinct values on an account is its device count.
Turning a device set into an enforcement signal
On the backend you keep, per account, the set of distinct hardware fingerprints seen. The size of that set over a rolling window is the device count you act on. A per-seat SaaS plan, a per-household subscription, and a single-user license each imply a cap; the account crossing it is the signal.
Treat the count as graded, not binary. A jump from two devices to nine over a weekend is a different story from a slow drift to four over a year as someone replaces a phone and a laptop. Rate of growth and a rolling window matter more than a lifetime total.
import { compareFingerprints } from 'doorman-benny';
// De-duplicate near-matches so one drifting device is not counted twice,
// then count distinct physical devices on the account.
function countDistinctDevices(stored: object[]): number {
const devices: object[] = [];
for (const candidate of stored) {
const isKnown = devices.some((known) => {
const cmp = compareFingerprints(known as any, candidate as any, { mode: 'cross-browser' });
return cmp.hardwareMatch || cmp.match;
});
if (!isKnown) devices.push(candidate);
}
return devices.length;
}cross-browser mode merges a device that drifted (a new monitor, an OS update) instead of counting it twice, so the total reflects real machines.
Handling legitimate multi-device users
The honest hard case is the legitimate user with several devices: a phone, a laptop, a work machine, a tablet. That is normal, not abuse, and a cap set too low punishes your best customers. Account-sharing detection is a policy decision wearing an engineering hat.
A few practices keep it fair. Set the cap to the real product boundary, since a household plan is not a one-device plan. Measure concurrent or rapid fan-out growth rather than a lifetime device total. On a breach, prefer a soft response, a prompt to confirm it is still you, a step-up verification, or an upgrade offer, over a hard lockout. And pair the device count with `result.consistency.spoofLikelihood`: a fleet of devices that also reads as spoofed looks like a credential-selling operation, not a large family.
Frequently asked questions
How is account sharing different from multi-accounting?
They are inverses on the same device graph. Multi-accounting is many accounts behind one device; account sharing is one account used across many devices. Both are measured from the same per-account, per-device map, just read in opposite directions.
Won't a single user with a phone and a laptop get flagged?
Those are two physical devices, so they produce two hardware fingerprints, which is the correct count. The point of cross-browser hardware identity is that the same person on two browsers of one machine counts as a single device, not two, so you only flag genuine device fan-out. Set the cap to your plan's real device boundary.
Can a sharer evade detection by switching browsers?
No. The hardwareFingerprint is identical across Chrome, Safari, Firefox, and Brave on the same machine, so opening a different browser does not create a new device. A genuinely different physical device does add to the count, which is exactly the behaviour you want.
How is this better than a concurrent-session limit?
A concurrency limit only catches simultaneous use, so two people who time-share one login never overlap and never trip it. Counting distinct devices over a rolling window catches sequential sharing that a session cap cannot see.
Does account-sharing detection need a server?
Fingerprint collection runs client-side. The per-account device set and its count live on your backend, and compareFingerprints is a synchronous function you run there to de-duplicate near-matches. There is no external service or API key in the detection itself.
Get started
Measure devices per account, not browsers
npm install doorman-benny. Cross-browser hardware identity and a built-in comparison API in the free tier. Count the real machines behind an account and enforce your seat or household limits.
Last reviewed June 17, 2026

