How to stop account takeover
Also called: ATO · credential stuffing · account hijacking · session hijacking · login fraud
What it costs you
- trending_downWhatever the account holds. Stored balance, loyalty points, saved cards, and any payout route the real owner set up that you now honour for a stranger.
- trending_downThe refund, almost always. When a customer gets compromised on your service, you generally make them whole no matter whose password it was.
- trending_downTrust, which does not come back as fast as the balance does. Users who get taken over on your platform tell people.
- trending_downSupport time at its most expensive. Proving who somebody is, recovering the account by hand, and dealing with a customer who is upset and right to be.
- trending_downLegal exposure where personal data sat in the compromised account. A different and slower kind of cost.
How the attack runs
- 01
Get passwords somebody else leaked
Almost never from you. Lists from other companies' breaches are cheap and enormous, and they work because almost everyone reuses passwords. The attacker is betting your users had an account somewhere that got breached.
- 02
Try the list against your login
Automated, spread across many addresses, and slow enough per account to slip under simple limits. A success rate of one or two percent is well worth it when the list has millions of rows.
- 03
Confirm it works, then wait
A careful attacker does not act straight away. They check the password works, note it down, and come back later, because acting immediately is what sets off alarms and what the victim notices.
- 04
Take over the recovery route
This is the step that turns a login into ownership. Swap the email, add a phone, remove a passkey, register a new device. Once recovery belongs to them, resetting the password does not get rid of them.
- 05
Get the value out through a route you allow
A withdrawal to a new destination, a gift card purchase, a points transfer. Rarely anything exotic, because your own normal features are the cleanest way out.
What does not work
These are the defences most teams try first. They are listed here because trying them and watching them fail is expensive.
Locking an account after N wrong passwords
It turns a takeover attempt into an attack on your own customers. Someone with a list can deliberately lock thousands of real users out, and your support queue becomes the attack. Slow them down and score them instead of locking.
Making people change passwords every 90 days
Every serious security body dropped this years ago. It pushes people toward predictable variations of the password they already reuse, which makes the stolen-list problem worse, not better.
SMS codes as your main defence
Better than nothing, and the weakest second factor there is. SIM swapping is real, and a fake login page can simply ask for the code and use it within the minute. Treat it as a step up, not as the answer.
Blocking VPN and proxy logins
A real share of your privacy-minded, travelling and corporate users are behind one permanently. As a hard rule this locks out real customers every day to inconvenience an attacker who will just buy a residential proxy.
Emailing 'a new device signed in'
Useful, and not a control, because it is a notification rather than a decision. If the attacker already changed the email, you have just told them it worked. Send it, but do not let it stand in for an actual challenge.
The signals that do
These are the signals that carry weight on account takeover, and why each one is the signal rather than the obvious alternative. Not all of them are ours: the ones marked you build this genuinely work and Kaidn does not check them, so you would be wiring them up yourself. Listing those unlabelled would read as a claim we cannot support.
It does not look like this user
you build thisThe defining signal of this whole category. There is a real person here with months of history, so you are not asking whether this looks suspicious in general. You are asking whether it looks like them. A new device, a new network, a new country and an odd hour, all at once, is a strong break.
The same list being worked across many accounts
Kaidn checks thisOne failed login is noise. Hundreds of accounts each seeing two or three failures from the same device, network or timing rhythm is a stolen list being tested. Invisible on one account, obvious across all of them.
Two logins too far apart to be real
Kaidn checks thisCrude, still useful, as long as you treat it as evidence rather than proof. Two successful logins from places nobody could travel between in the time available deserve a challenge. VPNs make it noisy, so it belongs in a score with a weight, not as a hard rule.
What happens right after login
Kaidn checks thisThe login itself is often unremarkable. Changing the email, phone or payout destination is not. Score those as their own events, with the login attached, and you catch the cases that already got through the door.
Bursts against password reset
Kaidn checks thisRecovery is the softer target and usually watched less than login. A spike against reset endpoints normally comes a week before the takeovers you will hear about.
Account takeover is the opposite of everything else on this site.
The other guides are about accounts that were never real. This one is about accounts that are completely real, belong to somebody who trusted you, and are now being used by a stranger.
That flip changes the detection problem completely, and it is worth saying out loud, because a tool tuned for one is often bad at the other.
You have history here, so use it#
With a fake signup there is nothing to compare against. With takeover there is a real person with months of behaviour behind them. The question is not "does this look risky". It is "does this look like them".
That is why generic risk signals do badly here. A home IP, an ordinary phone and a clean email are all perfectly normal for an attacker using a proxy in the victim's own country. They are also normal for the victim.
What separates them is the break from an established pattern. A device never seen on this account, on a network this user has never used, at an hour they have never been awake, going straight to the withdrawal page.
Almost none of this starts with you#
Credential stuffing runs on password reuse. The passwords come from somebody else's breach, so a compromised account usually is not evidence that your security failed.
It is also why "make our passwords stronger" is the wrong lever. Your users' passwords are already strong or weak elsewhere, and you cannot fix that with a rule on your signup form.
What you can do is spot the list being worked. That is visible across accounts even when each individual account only sees two or three failures.
Watch recovery harder than login#
If you harden one thing, harden this.
Login is where attention goes and where the attacker expects to be watched. Changing the email address, phone number, passkey or payout destination is where a session turns into permanent ownership, and it is very often scored less carefully, or not at all.
Score those as their own events, with the login that came before them attached. A password change from a device first seen four minutes ago is a completely different thing from one made on a laptop that has been signing in for two years. Treating both as "settings updated" is how accounts get lost.
Detection has a ceiling here, and authentication sits above it#
We will be straight about this, because it matters more on this page than anywhere else on the site.
The strongest control against credential stuffing is not scoring. It is a login that a stolen password cannot pass.
Passkeys end credential stuffing for the accounts that adopt them, in a way no detection layer can match. If you can move users onto them, do that first. Treat everything below as cover for the accounts that have not moved yet, for the recovery flows, and for the phishing cases where the user handed over a live code themselves.
Anyone selling you detection as a replacement for that is selling you the second best option as the first.
Where we sit#
We score login and post-login events using the account's own history as context, plus device, IP and network reputation, how fast the same attempts are spreading across accounts, and email identity. Verdicts come back with the checks that fired and what each one counted for, so you can trigger a challenge on the specific signal you care about.
We do not ship the challenge itself. We return a verdict. Issuing the step-up, the passkey prompt or the recovery hold is yours to build. Tools built specifically around the login moment, such as Castle, do that part and do it well. Running both is a sensible architecture, not a hedge.
Score the recovery path as its own event#
The login call is the one everybody writes. This is the one that matters more:
// a change of recovery address is where a session becomes ownership const r = await kaidn.score({ event: "email_change", user_id: user.id, ip: req.ip, email: body.newEmail, device_id: body.kaidn_device_id, }); // how new this device is ON THIS ACCOUNT is the real tell, and that data is yours const deviceIsNew = !(await db.accountDevices.exists({ user_id: user.id, device_id: body.kaidn_device_id, })); if (deviceIsNew || r.verdict !== "allow") { // do not refuse; delay and notify the address you already had await holdChangeFor(user.id, "24h"); await notifyOldAddress(user, r.event_id); return { status: "pending_confirmation" }; }
A 24-hour hold plus an email to the address being replaced is worth more than any score on this flow. It gives the real owner the one thing the attacker needs them not to have, which is time.
The session signals worth acting on#
Takeover shows up in session risk. These are the reasons the engine returns for it:
| reason | check | what it is saying |
|---|---|---|
impossible_travel | sessionRisk | two logins too far apart to be the same person |
device_ip_hopping | sessionRisk | one device moving between networks faster than a person moves |
ip_cloaking | sessionRisk | the network is being hidden mid-session |
vpn_drop | sessionRisk | a VPN dropped and revealed a different location |
velocity | velocity | attempts across accounts at a rate no human produces |
const r = await kaidn.score({ event: "login", user_id: user.id, ip: req.ip, email: user.email, device_id }); // Kaidn returns the verdict; issuing the step-up is yours if (r.reasons.includes("impossible_travel") || r.verdict === "block") return stepUp(user, r.event_id); if (r.verdict === "review") return requireEmailConfirm(user, r.event_id);
Something to check this week#
Search your last ninety days for accounts whose recovery email changed within an hour of a login from a device that account had never seen before.
That one query costs nothing. It finds takeovers that already happened, and it tells you whether this is a problem you have or a problem you are worrying about.
Frequently asked questions
What is account takeover?
A stranger operating an account that is completely real and belongs to somebody who trusted you. It is the opposite of every other pattern on this site, where the account was never real to begin with. That flips the detection problem: here you have months of genuine behaviour to compare against, and the question is not whether this looks risky but whether it looks like them.
Does a compromised account mean my security failed?
Usually not. Credential stuffing works on password reuse, and the passwords come from somebody else's breach. What you can do is spot the list being worked, which is visible across accounts even when each account only sees two or three failed attempts.
Will stronger password rules help?
Very little. Your users' passwords are already strong or weak somewhere else, and a complexity rule on your form does not change what they reused on a site that got breached two years ago. The lever that works is a login a stolen password cannot pass.
What is the single most effective control?
Passkeys, and it is not close. For the accounts that adopt them, credential stuffing simply stops working in a way no detection layer can match. Detection covers the accounts that have not moved over, the recovery flows, and the phishing cases where the user handed over a live code themselves. Anyone selling detection as a replacement for passkeys is selling you the second best option as the first.
Which flow should I harden if I can only do one?
Recovery, not login. Login is where everyone's attention goes and where the attacker expects to be watched. Changing the email address, phone number, passkey or payout destination is where a session becomes permanent ownership, and it is very often scored less carefully or not at all. Score those as their own events, with the login that came before them attached.
Score your own traffic for this
10,000 events a month free, no card. Every verdict comes back with the checks that fired and their weights, so you can see which signal caught it rather than trusting a number.