Orange textured background

Anti-fraud

How to detect account sharing with a cross-browser device limit

Account sharing is the mirror image of multi-accounting: one account spread across many devices. The fix is to count physical machines, not browsers or sessions.

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.

The end-to-end flow

StepWhat happens
1. CaptureAt each login, call getFingerprint() client-side. You get fingerprint, hardwareFingerprint, and consistency. No server involved yet.
2. SendPOST the two hashes plus the consistency result to your backend over your existing API along with the account credentials. Decide server-side: never trust a verdict computed in the browser.
3. StorePersist a record keyed by accountId: { accountId, devices: [{ hardwareFingerprint, fingerprint, firstSeen, lastSeen }] }. One account row holds the set of distinct devices that have accessed it. You own this store; the library is client-only.
4. Look upOn each login, load the account's device set and check whether the incoming hardwareFingerprint is already present. For hardware drift tolerance, run compareFingerprints in cross-browser mode against stored device records for a matchScore.
5. DecideIf the incoming device is new (not in the set and no high matchScore) and the set is already at your threshold N, the login is flagged. Layer in consistency.spoofLikelihood to catch anti-detect browsers generating fresh hardware identities.
6. Act and logBlock new device logins over the limit, require a plan upgrade, or trigger a re-authentication flow. Log which device was flagged and update lastSeen on the matched device record.

Account-sharing gate by device count

ConditionOutcome
Device already in the account's setKnown device: allow login, update lastSeen.
New device, account device count below threshold NUnder limit: add device to the set and allow login.
New device, account device count at or above threshold N, low spoofLikelihoodOver limit: block new device login. Prompt to remove an old device or upgrade the plan.
High spoofLikelihood on the incoming deviceEvasion signal: challenge before allowing login regardless of device count.
No exact match, but high matchScore in cross-browser mode against an existing deviceProbable returning device: allow login and update the matched record rather than adding a new entry.

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.

Why key the store on accountId instead of hardwareFingerprint?

Because the fraud pattern is the inverse: instead of one device spawning many accounts, one account is accessed by many devices. Keying on accountId lets you hold the complete set of devices per account and count them directly. The hardwareFingerprint is still the value you store in that set.

What should I do when a user hits the device limit legitimately?

Show a device management screen where they can review active devices and remove old ones (a phone they no longer use, a laptop they replaced). This is both better UX than a hard block and a natural conversion path for users who genuinely need more devices to upgrade their plan.

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.

Get startedcompareFingerprints API reference

Related posts

Last reviewed June 17, 2026