Skip to main content
Hoe cside accountdeling detecteert
Language

Accountdeling detecteren

Voorkom ongeautoriseerd delen van accounts en handhaaf license seats. Tag telemetry met het account dat is ingelogd, en lees vervolgens de regel account_shared_detected vanuit uw backend om te zien wanneer één account vanaf meer apparaten wordt gebruikt dan u toestaat.

Eén set inloggegevens, gebruikt door meer mensen dan ervoor betaald hebben: het gedeelde abonnement, de twee seats waarop in stilte acht mensen draaien, het doorverkochte account. Niets daarvan valt op in uw eigen logs, want elke login is een geslaagde login met het juiste wachtwoord.

Device Intelligence voegt toe wat de logs missen: een apparaatidentiteit bij elke sessie. Tag uw telemetry met het account dat inlogt, en het oordeel bereikt uw backend tijdens het inloggen zelf. In het dashboard krijgt u ook de vorm ervan te zien: welke apparaten, van waar ze inloggen, of sessies elkaar overlapten, en of het account sneller van regio wisselde dan iemand kan reizen.

Hetzelfde mechanisme handhaaft license seats. Stel de limiet in op het aantal verkochte seats, en de regel laat u weten wanneer een klant er in stilte overheen is gegroeid.

Hoe cside accountdeling detecteert

Tag uw telemetry met de account-id van wie er is ingelogd. cside telt het aantal unieke apparaten dat voor dat account is gezien binnen een voortschrijdend venster, en zodra dat aantal de limiet van uw site overschrijdt, gaat de regel af.

Dit zijn twee helften, en ze draaien op verschillende plekken:

  • Uw browser roept sendClientTelemetry({ userId }) aan en krijgt een token terug.
  • Uw server stuurt dat token naar cside en leest het oordeel.

Houd de oordeelscontrole op uw server. Uw Device Intelligence API-key mag niet in de buurt van browsercode komen, en een oordeel dat de browser kan lezen, is een oordeel dat de browser kan vervalsen.

Wat u nodig heeft

  • Device Intelligence ingeschakeld op het domein
  • Het script geïnstalleerd en sendClientTelemetry wordt aangeroepen: zie Device Intelligence instellen
  • Een backend Device Intelligence API-key: zie Events API
  • De regel ingeschakeld voor uw domein. Self-serve activatie bestaat nog niet, dus vraag cside om deze in te schakelen

Het Device Intelligence-installatiescherm met het npm-tabblad geselecteerd, met het installatiecommando en de aanroep van initDeviceIntelligence Het Device Intelligence-instellingentabblad met een actieve backend API-key, met een herinnering om keys server-side te houden

In de browser: stuur de account-id

Tag telemetry met userId

Roep sendClientTelemetry eenmaal aan bij het laden van de pagina, zonder userId, zodat de verzameling vroeg begint, en roep het daarna nogmaals aan met userId zodra u weet wie er is ingelogd. Geef het token dat wordt teruggegeven direct door aan uw eigen backend; de browser doet er verder niets mee.

U kunt het opnieuw aanroepen wanneer u een verse meting wilt, niet alleen bij het inloggen. Elke aanroep levert een nieuwe evaluatie op, dus bij langlopende sessies houdt een periodieke aanroep het apparaataantal actueel in plaats van u achter te laten met een oordeel van het moment waarop de persoon voor het laatst inlogde.

import { initDeviceIntelligence } from "@cside.dev/device-intelligence";

const { sendClientTelemetry } = await initDeviceIntelligence({
  teamID: "[your-team-id]",
});

// Call this at sign-in, and anywhere else you want to refresh the reading:
// long-running sessions benefit from a periodic call.
async function onSignIn(userId) {
  const { token, errors } = await sendClientTelemetry({ userId });

  if (errors) {
    console.error(errors);
    return;
  }

  // Your own backend route, not a cside endpoint. It does the actual
  // verdict check server-side; see the next section.
  await fetch("/api/account-sharing-check", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ token }),
  });
}
De key moet overeenkomen met wat de regel leest

userId is de standaard, geen vereiste. Wat telt, is dat de key die u stuurt en de key die de regel leest, identiek gespeld zijn. Stuurt u accountId of user_id naar een regel die nog op userId staat ingesteld, dan is dat geen fout: de waarde wordt permanent als ontbrekend gelezen, en u ziet bij elke evaluatie outcome: 3 in plaats van outcome: 1 of outcome: 2. Stuurt u al een andere key, dan kan cside de regel daar in plaats daarvan op richten. Zie Stuurt u al een andere id?.

Geen PII of geheimen in external-id-waarden

userId en elke andere external id worden opgeslagen en meegenomen in exports. Stuur een ondoorzichtige account-identifier, geen e-mailadres, naam of iets anders wat u niet zou willen bewaren.

Stuurt u al een andere id?

U hoeft niet over te stappen op userId. Wat telt, is dat de regel dezelfde key leest als die u stuurt, en cside kan de regel omkoppelen, dus er verandert niets in uw client.

De key moet wel een van userId, accountId, organizationId of teamId zijn. Dat zijn de enige vier waarop de regel kan keyen, omdat keyen een veld met een begrensde set waarden vereist. email, orderId en al het andere kunt u nog steeds sturen en opslaan, alleen kunnen ze niet de key zijn, dus stuur een van de vier mee naast wat u al gebruikt.

Op uw server: lees het oordeel

Dit is de route /api/account-sharing-check uit de vorige stap. Deze wisselt het token in voor de volledige evaluatie en bepaalt wat er gebeurt, volledig server-side, met uw Device Intelligence API-key.

curl https://api.cside.com/token/v2/verify \
  --request POST \
  --header "Authorization: Bearer $CSIDE_FINGERPRINT_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"token":"'"$CSIDE_FINGERPRINT_SESSION_TOKEN"'"}'

Het endpoint retourneert 202 Accepted met { "status": "pending" } zolang de evaluatie nog loopt, en daarna 200 OK zodra deze klaar is. Poll ongeveer 3 keer, met zo’n 3 seconden ertussen als veilige bovengrens: de rule engine draait synchroon als onderdeel van het opslaan van het resultaat, dus het oordeel is doorgaans al bij uw eerste poll klaar. En het is volledig klaar: rule_evaluations_v2 is gevuld op het exacte moment dat status "completed" wordt, nooit later, dus zodra u "completed" ziet, is een ontbrekende entry echt afwezig, niet nog onderweg. Loopt u uit uw pogingen en staat het nog op pending, behandel het dan als geen oordeel voor deze login in plaats van de persoon te blokkeren: de volgende login levert een verse evaluatie op.

Zo ziet het er helemaal bedraad uit: wissel het token in, controleer of het bij het account hoort dat daadwerkelijk is ingelogd, en zoek dan de entry voor accountdeling op en vertak daarop. Controleer zowel outcome als evaluated_as in de code zelf, niet alleen de regel-slug: andere regels, inclusief het schaduwexperiment van iemand anders, kunnen in dezelfde array meerijden.

// apps/api-style illustrative route handler. Adjust to your own framework.
// The wire sends these as integers. Naming them keeps the check readable;
// the full set of values is in the tables below.
const OUTCOME_TRUE = 1;
const LIFECYCLE_ACTIVE = 3;

app.post("/api/account-sharing-check", async (req, res) => {
  const { token } = req.body;
  const evaluation = await verifyToken(token);

  // Trust your own session, never the browser. The userId on the telemetry is
  // whatever the page chose to send, so bind it to the signed-in account
  // before you act on anything.
  if (evaluation.fingerprint?.external_ids?.userId !== req.session.userId) {
    return res.sendStatus(409);
  }

  const sharing = findAccountSharing(evaluation);

  if (sharing !== undefined) {
    // This account is over your site's device limit right now.
    // Step up auth, cap sessions, flag for review: see "Decide what to do" below.
  }

  res.sendStatus(204);
});

async function verifyToken(token, attempt = 0) {
  const response = await fetch("https://api.cside.com/token/v2/verify", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CSIDE_FINGERPRINT_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ token }),
  });

  if (!response.ok) {
    // An expired key, a bad token, a rate limit. Never fall through: a failed
    // request is an outage, not a verdict of "nobody is sharing this account".
    throw new Error(`Device Intelligence verify failed: ${response.status}`);
  }

  const data = await response.json();

  if (data.status === "pending" && attempt < 3) {
    await new Promise((resolve) => setTimeout(resolve, 3000));
    return verifyToken(token, attempt + 1);
  }

  if (data.status === "failed") {
    // Evaluation itself failed. Don't treat this the same as "no entry":
    // a failure isn't the healthy case.
    throw new Error("Device Intelligence evaluation failed");
  }

  return data;
}

function findAccountSharing(data) {
  // An absent entry means the rule wasn't evaluated, not that it
  // evaluated to false.
  return data.rule_evaluations_v2?.values?.find(
    (v) =>
      v.evaluation.rule_slug === "account_shared_detected" &&
      v.evaluation.outcome === OUTCOME_TRUE &&
      // Shadow rules ride in the same array. Acting on one would enforce
      // an experiment that isn't live for your site.
      v.evaluation.evaluated_as === LIFECYCLE_ACTIVE,
  );
}
Koppel het token aan uw eigen sessie

De userId op een token is wat de browser heeft meegestuurd. Iemand kan die weglaten, of er een ander account in zetten, dus als u het als identiteitsbewijs behandelt, kan die persoon waarnemingen op een account duwen dat niet van hem is, of zijn eigen account schoon laten lijken. Vergelijk fingerprint.external_ids.userId met het account in uw serversessie en weiger alles dat niet overeenkomt. cside vertelt u wat de browser beweerde; alleen u weet wie er daadwerkelijk is ingelogd.

outcome en evaluated_as zijn integers, geen strings. Elke geëvalueerde, gepubliceerde regel rapporteert bij elk event een entry, met welke outcome dan ook: 1, 2 en 3 zijn stabiele toestanden die u continu zult zien, geen eenmalige uitschieters. 4 is de uitzondering, en betekent iets specifiekers dan het lijkt: de regel heeft een meting nodig die wel bestaat, maar nog niet is opgewarmd. Dat is iets anders dan een status van "pending", wat betekent dat de evaluatie zelf nog niet klaar is en waar de pollinglus hierboven voor dient. Hetzelfde token harder pollen maakt van een 4 geen oordeel; die verdwijnt bij latere evaluaties naarmate er data voor dat account wordt opgebouwd.

Dezelfde enum als de rule_evaluations_v2-referentie; hier is wat elke waarde voor deze specifieke regel betekent:

outcomeBetekenisWanneer u dit ziet
0Niet gespecificeerdWordt niet geproduceerd
1TrueContinu, zolang het account boven uw limiet zit
2FalseContinu, de normale toestand voor een gezond account dat niet boven de limiet zit
3Ontbrekend (structureel)Continu, als de regel userId niet uit uw telemetry kan lezen, bijvoorbeeld door een verkeerde key
4Pending (tijdelijk)Wanneer de meting van de regel nog niet is opgewarmd. Niet hetzelfde als status: "pending", en dit token opnieuw pollen maakt het niet ongedaan
evaluated_asBetekenis
1Draft
2Shadow: iemands experiment, kan op elk moment verschijnen, onderneem er geen actie op
3Active: de regel die uw site heeft ingeschakeld
4Archived

Nog één ding over transition, handig als u liever eenmalig dan bij elk request iets doet: zodra u al outcome: 1 heeft gecontroleerd, betekent transition: 3 dat het account bij deze evaluatie net over de drempel is gegaan, en transition: 2 dat het al sinds een eerdere evaluatie over de drempel zit.

Het account dat de regel triggerde, staat in dezelfde response onder fingerprint.external_ids, niet onder evaluation.key. key is een hash van waarop de regel keyde en bevat nooit de account-id in platte tekst. Let ook op dat key.key_hash een JSON-string is, geen getal, en dat deze responsebody twee tijdstempelvormen mengt: created_at en updated_at op het hoogste niveau zijn getallen in milliseconden, terwijl evaluated_at en event_time binnen de evaluatie { seconds, nanos }-objecten zijn.

Ingekort voorbeeld van een response, vastgelegd uit een echte evaluatie, overige velden weggelaten. Let op de tweede entry: andere regels rijden echt mee in dezelfde array, en daarom matcht de code hierboven op slug, outcome en evaluated_as samen, niet op slug alleen.

{
  "status": "completed",
  "created_at": 1789731723590,
  "updated_at": 1789731723590,
  "fingerprint": {
    "device_id": "dev_3f8a21c9",
    "external_ids": {
      "userId": "acct_9f2a1c"
    }
  },
  "rule_evaluations_v2": {
    "values": [
      {
        "evaluation": {
          "rule_slug": "account_shared_detected",
          "outcome": 1,
          "evaluated_as": 3,
          "transition": 3,
          "key": { "key_hash": "16386830586737169487" },
          "event_time": { "seconds": 1789731723, "nanos": 590000000 },
          "evaluated_at": { "seconds": 1789731723, "nanos": 605000000 }
        }
      },
      {
        "evaluation": {
          "rule_slug": "multi_account_detected",
          "outcome": 2,
          "evaluated_as": 3,
          "transition": 2,
          "key": { "key_hash": "261153104274697293" },
          "event_time": { "seconds": 1789731723, "nanos": 590000000 },
          "evaluated_at": { "seconds": 1789731723, "nanos": 605000000 }
        }
      }
    ]
  }
}

Niet hierboven getoond: sommige geneste objecten in de volledige response gebruiken camelCase-veldnamen in plaats van snake_case, waar ze een wireformat delen met de rule designer van het dashboard (binnen explanation, en key.components[].dimension). Als u uw eigen volledige response afdrukt en een mix van beide ziet, is dat te verwachten, geen bug in uw integratie.

Zie de volledige rule_evaluations_v2-veldreferentie voor de rest van de velden.

Bepaal wat u ermee doet

cside rapporteert, het blokkeert niet. Wat u doet met een afgaande regel, is aan u: stap over op sterkere authenticatie, beperk gelijktijdige sessies voor het account, of markeer het voor handmatige beoordeling.

Bevestig dat het werkt

Controleer eerst of de id daadwerkelijk binnenkomt: wissel één token in en controleer of fingerprint.external_ids de verwachte waarde onder userId bevat.

Controleer dan de regel zelf, voordat iemand ook maar iets deelt. Omdat elke geëvalueerde, gepubliceerde regel bij elk event rapporteert, hoeft u geen deling te veroorzaken om te zien of de pipeline werkt:

  • outcome: 2 op de entry account_shared_detected is hoe een gezonde, correct bedrade integratie eruitziet: de regel draait, leest de juiste key, en niemand zit boven de limiet. Dit is uw negatieve bevestiging, en u zult het continu zien, niet slechts eenmalig.
  • outcome: 3 betekent dat u de verkeerde key stuurt. Los dit nu op, voordat u verder test; zie Stuurt u al een andere id?.
  • Helemaal geen entry voor account_shared_detected betekent dat de regel nog niet is geactiveerd voor dit domein, dat het een meet-only regel is die niet publiceert, of dat er voor dit event geen evaluatie liep. Vraag het cside.
voortschrijdend venster van 2 uur0u1u2u3uApparaat 1Apparaat 2Apparaat 3overschrijdt de limiet van uw siteApparaat 4Latere login, buiten het venstertelt niet mee

Zie het omslaan naar true

Zodra u de gezonde outcome: 2-basislijn hierboven heeft bevestigd, trigger de regel dan opzettelijk om de volledige flow te zien, inclusief de transitie:

  1. Log in op één testaccount vanaf drie verschillende browsers of apparaatprofielen, binnen een tijdsspanne van 2 uur. Dat zijn de standaardwaarden zoals uitgeleverd; als de limiet of het venster van uw site is aangepast, gebruik dan die van uzelf
  2. Wissel na elke login een sessietoken in en kijk naar account_shared_detected: het blijft outcome: 2 voor de eerste twee, en slaat om naar outcome: 1 (met transition: 3) zodra het derde apparaat het account boven de limiet van uw site brengt
  3. Bevestig evaluated_as: 3 ernaast, zodat u weet dat u de actieve regel leest en geen schaduwexperiment

Privénavigatievensters op dezelfde fysieke machine worden mogelijk niet als afzonderlijke apparaten gelezen, gebruik dus aparte browsers of apparaatprofielen in plaats van meerdere privévensters in één browser.

curl https://api.cside.com/token/v2/verify \
  --request POST \
  --header "Authorization: Bearer $CSIDE_FINGERPRINT_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"token":"'"$CSIDE_FINGERPRINT_SESSION_TOKEN"'"}'

# rule_evaluations_v2 carries the rule outcomes, account_shared_detected among them.
# If "status" is "pending", wait a few seconds and retry, up to about 3 times.

Zodra u het in de API heeft bevestigd, open die sessie dan in het cside-dashboard: de sessiedetailpagina heeft een tabblad Identity graph dat hetzelfde visueel laat zien, één account dat uitwaaiert naar meerdere apparaatnodes.

Een sessie geclusterd op de external id userId, met vier sessies verdeeld over vier apparaten De identity graph voor één account, uitwaaierend via vier browsers naar vier apparaten en vier netwerken, met de regel voor gedeelde accounts geactiveerd

Als uw team toegang heeft tot de use case-pagina voor accountdeling in het dashboard, is de coverage tile de meest directe manier om een verkeerde key op te sporen: die meet welk deel van uw events daadwerkelijk de id bevat die de regel leest. Die pagina is nog in early access, dus mogelijk staat deze bij u nog niet aan.

Het scherm Account sharing met twee gedetecteerde accounts, waarbij één is geselecteerd met de bijbehorende apparaten gegroepeerd per browser

Volgende stappen

Problemen oplossen

Vrijwel elke integratie die niets ziet, heeft dezelfde oorzaak: de external id die u stuurt, is niet de id die de regel leest. userId is de standaard, geen vereiste, dus cside kan de regel omkoppelen naar elk van de andere keybare velden. Uw telemetry en de regel hoeven het alleen met elkaar eens te zijn over één veld.

  1. Controleer wat er binnenkwam. Wissel een token in en kijk naar fingerprint.external_ids. Staat uw account-id daar niet bij, dan liep sendClientTelemetry zonder deze, of vond de aanroep plaats voordat u wist wie er inlogde.
  2. Controleer of de regel het kon lezen. Een outcome van 3 op de entry account_shared_detected betekent dat de regel draaide maar zijn key niet kon vinden.
  3. Controleer of de waarde stabiel is. Een sessie-id of een roterend token laat elke login als een nieuw account overkomen, waardoor er nooit iets tegen één account wordt opgebouwd.
  4. Koppel om in plaats van te herschrijven. Vraag cside om de regel op uw key te richten. Zie Stuurt u al een andere id?.
Was this page helpful?