Account takeover

The credentials matched.The machine did not.

One verify call at the login says whether the machine behind the password has ever signed this account in.

The machine at this login

returning
arrived from
browser
trust score
--/ 100
01The burst

Eight accounts, one machine, ninety-four seconds

Credential stuffing arrives as ordinary logins, one account at a time.

A recorded login burst9f21c04e7db35a18
m.oja@…priit.saar@…l.tamm@…annika.k@…j.rebane@…t.laine@…k.mets@…h.pärn@…
8accounts tried from one machine in 94 seconds, 1 password matched
risk
integrity.automation+40
network.datacenter+26
velocity.device+22
12/ 100STUFFING
trust score
02The fork

A correct password is where the decision starts

The password matchedEvery session below has cleared it

A machine this account has signed in from

returning trueid on filenothing raised
Sign in

A machine it has never seen

returning falsefirst_seen nowhosting network
Step up
03Your session

Your session, read the way a login gate reads it

Has this account's owner signed in from this machine?identity.returning
Is a driver or a headless build in play?integrity.automation
Is the environment rewritten?integrity.tampered
Where did the request arrive from?network
How hard is this machine hitting you?velocity.device
What argued against the session?risk.reason_codes
--of the 6 readings a login gate takes came back clean
Read the rest of the responsethe playground scans the browser you are using
04Wiring

One call on the client, one check before the session

login.tsxCLIENT
import { useTrustSig } from "@trustsig/react";

const { getResponse } = useTrustSig();
const { token } = await getResponse();
login.jsSERVER
import { TrustSig } from '@trustsig/server';

const ts = new TrustSig({ secretKey: process.env.TRUSTSIG_SECRET_KEY });

app.post('/login', async (req, res) => {
  const token = req.headers['x-trustsig-response'];
  const result = await ts.verifyRemote(token);

  // Fail closed: proceed only on an explicit ALLOW.
  if (result.action !== 'ALLOW') {
    return res.status(403).json({ error: 'Access denied.' });
  }

  const user = await authenticate(req.body.email, req.body.password);
  const { device_id, degraded } = result.identity;

  // Your table: the machines this account has signed in from before.
  const known = degraded || (await devicesFor(user.id)).includes(device_id);

  if (!known) {
    return sendSecondFactor(user, { request_id: result.request_id, device_id });
  }

  return completeLogin(req, res, { user, device_id });
});
05Limits

What the reading at login does not claim

A new machine is usually a new phoneidentity.returning
An upgrade and an attacker both come back as returning false, so the network and integrity readings decide which one you are looking at.
One id can cover two peopleidentity.device_id
A family desktop signs two people in, which is why a known id raises confidence without settling anything.
Some ids name a cohortidentity.degraded
A locked-down browser can produce an id a crowd shares, so an unfamiliar id is not always an unfamiliar machine.
The verdict is returned, never enforcedaction
The response carries the grade and the evidence behind it, and your login route decides what to do with both.
06 Answers

Takeover at login, answered

identity.returning and identity.first_seen say whether this account has ever signed in from this machine. A correct password from a machine with no history becomes a step-up instead of a silent success.

Once, on the first sign-in from it. A new machine is a new id by design, so the honest new phone and the attacker's machine look alike on arrival. The network block, the integrity findings and integrity.automation are what tell them apart.

One machine against many accounts, so velocity.device runs high while each login looks ordinary on its own. integrity.automation reports a driver or a headless build, and network.datacenter reports the hosting range the burst arrived from.

No. It decides who needs to be asked. A returning machine with a clean reading signs in, and a session with no history behind it gets the second factor you already have.

No. The token is verified server side and the nonce is spent at the edge, so a captured token is not a reusable device. identity.device_id arrives in the verify result on your server, never in anything the browser sends.

Then the device is genuinely known and device intelligence says so. Resolving whether the person behind a known machine changed is TrustSig Pro.

Read the machine before the session opens.

Two calls, and the login has the machine's history before it opens the session.