Account Takeover Prevention

Stop account takeover from inside your app.

srvAudit ATP sits on your login path, not in front of it — so it scores behaviour instead of addresses. Two calls to wire up, and it blocks nothing until you say so.

Ships observing-only. You look at the numbers before anything is ever blocked.

Built for automated credential abuse

Different names, one shape: software working through a list against your login form, patient enough to stay under every threshold you have.

Credential stuffing

Username and password pairs from somebody else's breach, replayed against your login. Each account gets one or two attempts, so nothing looks unusual per account or per address.

Password spraying

A handful of predictable passwords tried against every account you have, paced slowly enough that no individual account ever reaches a lockout threshold.

Targeted brute force

One valuable account, many passwords, spread across a wide enough pool of addresses that a per-address limit never sees a pattern.

Scripted clients

Traffic presenting your own app's user agent and headers, with a device identifier that changes on every request so it can never be pinned down or banned.

Many addresses, one behaviour. Detect the behaviour, not the addresses.

Every one of these is engineered to sit under whatever limit you set. Rate limits, reputation lists and geo-blocks are defeated by the same arithmetic. What separates an attacker from your users is not where they come from — it's how they behave.

A worked example

Why the arithmetic beats you

An illustrative distributed stuffing campaign. The magnitudes vary enormously between targets — the ratio is the part that stays the same.

40,000
login attempts / hour
against one endpoint
18,000
distinct addresses
consumer broadband and proxies
2.2
attempts per address
fewer than a forgetful user
6%
on any reputation list
the rest look ordinary

At a couple of attempts per address, any limit loose enough to let one real person log in also lets the whole campaign through. Tighten it and you start rejecting families behind a shared connection, offices behind one gateway, and anyone who fat-fingers a password twice. There is no setting that separates them, because the distinguishing information isn't in the address at all.

What a network-edge product can't see

AWS WAF ATP is a capable product. It is simply standing in the wrong place for this class of attack.

It knows logins are failing. Not whose.

An edge product reads your status codes and gets an aggregate failure rate — a blunt number that rises on a bad Monday morning as readily as during an attack. It cannot attribute a failure to an account, so it cannot tell one account being sprayed from a thousand people mistyping. Acting on the aggregate means acting on everyone.

The addresses are clean

A residential-proxy botnet has ordinary consumer reputation. Anonymous-IP lists catch under 5% of it. Reputation-based signals barely register.

The client is a convincing forgery

A valid user agent, the full set of your app's custom headers, and a device identifier that rotates on every single request. Edge fingerprinting sees a plausible app because it is being shown one.

Per-account history stays on your server

Which account was tried, how often, from how many devices, and whether the last attempt was a wrong password or a correct one awaiting a second factor — none of that leaves your application. To get it, an edge product would need you to hand it your login payloads. We never ask for them.

Two mechanisms. That's the whole product.

Everything else is configuration and telemetry. Both mechanisms are levers you turn at runtime, without a deploy.

Mechanism 01

The token gate

A client solves a small proof-of-work puzzle on-device to mint a short-lived signed token, then attaches it to the login request. Address rotation is irrelevant to did you complete the handshake, so spreading a campaign across tens of thousands of addresses buys nothing.

  • Validated statelessly — a constant-time signature check, no database read on your login path.
  • Difficulty is a live dial. Each extra bit doubles the attacker's minting cost.
  • Keys rotate with zero downtime: sign with the current key, verify against current and previous.
  • Per-address mint caps and one-time redemption stop bulk token farming and replay.
Mechanism 02

Behavioural scoring

For the attacker who does hold a valid token. The governing principle is positive evidence, not a blocklist: a request has to prove it belongs to a real session. A fresh identifier with no history can't prove anything — which is exactly why rotating identifiers doesn't evade it, and why it can never be turned into a way to lock out one of your users.

50

Impossible client version

Claims a version that was never shipped

40

No session provenance

Jumped straight to login, skipped your app's startup sequence

30

Invariant fingerprint

A field a real population varies, held constant

30

Device account spread

One device, many distinct accounts

Block threshold: 60. No single weight reaches it, so a block always needs at least two independent signals agreeing. One noisy signal can never block on its own.

It cannot take your logins down

A protection that can break authentication is a worse problem than the one it solves. Four properties make that structurally impossible.

Fails open

The client in your app uses a 50ms connect and 150ms read budget and swallows every exception. Any error or timeout is treated as allow. An outage on our side costs you protection, never logins.

Observes before it enforces

Every site ships in count mode. The verdict is always allow; you get the would-have-blocked volume as telemetry. You decide to enforce after looking at real numbers.

One-field rollback

Switching back to observing is a single field in the portal, live within a minute. No code rollback, no redeploy, ever.

Cannot lock out an account

A request carrying a valid token is never blocked by the per-account threshold. Otherwise an attacker could burn a victim's counter with two cheap puzzles and lock the real owner out.

The attacker sets your AWS bill. Not ours.

AWS WAF ATP charges $1.00 per 1,000 login attempts inspected, on top of the WAF base fees. The inspection you're paying for scales directly with the attack — the harder you're hit, the more the protection costs. Our plans are flat, with overage priced 50× lower.

Starter

$19 / month

per protected site

  • 250,000 login attempts / month included
  • Then $0.02 per 1,000
  • 30 days of telemetry retention
  • Token gate and behavioural scoring
  • Every lever tunable at runtime
  • Managed from your srvAudit portal

AWS WAF ATP at the same volume: $265.15 / month

Choose Starter
Most popular

Growth

$49 / month

per protected site

  • 2,000,000 login attempts / month included
  • Then $0.015 per 1,000
  • 90 days of telemetry retention
  • Token gate and behavioural scoring
  • Every lever tunable at runtime
  • Managed from your srvAudit portal

AWS WAF ATP at the same volume: $2,016.20 / month

Choose Growth

Scale

$149 / month

per protected site

  • 10,000,000 login attempts / month included
  • Then $0.01 per 1,000
  • 365 days of telemetry retention
  • Token gate and behavioural scoring
  • Every lever tunable at runtime
  • Managed from your srvAudit portal

AWS WAF ATP at the same volume: $10,021.00 / month

Choose Scale

What the same month costs

AWS figures use published list prices: $5 web ACL, $10 ATP rule group, $0.60 per million requests, and $1.00 per 1,000 login attempts inspected.

Login attempts / month AWS WAF ATP srvAudit ATP You keep
250K A small app, quiet month $265.15 $19.00 Starter 92.8% $246 / month
2M A busy app, normal traffic $2,016.20 $49.00 Growth 97.6% $1,967 / month
10M Large app, or light attack $10,021.00 $149.00 Scale 98.5% $9,872 / month
90M Under sustained credential stuffing $90,069.00 $949.00 Scale 98.9% $89,120 / month

The bottom row is the point. Under sustained attack, AWS bills you for every one of the attacker's attempts.

What we deliberately don't do

srvAudit ATP is the in-app layer. It is more basic than AWS's offering by design, and it is not a replacement for a WAF. If you need edge filtering or volumetric defence, run both — they solve different problems and ours costs a rounding error next to the attack it's absorbing.

It is also a layered, evolving defence rather than a wall. Each behavioural counter forces an attacker to impersonate more of your application's real lifecycle — more requests, more cost, more fingerprintable surface. That cost shift is the win, not a permanent block.

× No database of known-compromised credentials. AWS has one; we don't.
× No CAPTCHA, no managed edge rule groups, no CDN integration.
× Not a DDoS or volumetric product. Put a WAF in front of us for that.
× A botnet of real devices running your real app needs hardware attestation, which is a later phase.
The real authentication result, including MFA-pending and locked states.
Full request context: device, client version, fingerprints, and pre-login behaviour.
Per-account failure accounting that works at zero client adoption.
Every threshold, weight and mode changeable at runtime with no deploy.

Three calls on your auth path

Ask before you authenticate, tell us what actually happened afterwards, and mark the pre-login steps a real client performs. Every call is fail-open with a tight timeout, so the worst case is that you lose protection — never a login.

verify
Before you authenticate
Should I allow this attempt? Returns a verdict, the reasons behind it, and the behavioural score.
report
After you authenticate
What actually happened — success, failure, MFA pending, or locked, attributed to the account that was tried. This is what turns a raw failure count into per-account accounting.
observe
On your pre-login endpoints
This client just performed startup step X. Builds the session provenance the boot-trail signal reads.
Read the integration guide
LoginController.php
public function store(LoginRequest $request)
{
    $context = Atp::contextFrom($request);

    // Fails open: any error or timeout allows.
    if (Atp::verify($context)->blocked()) {
        return back()->withErrors([
            'email' => __('auth.failed'),
        ]);
    }

    $result = $this->attemptLogin($request);

    // Attributes the outcome to the account that was tried.
    Atp::report($context, $result);

    return $result->successful()
        ? redirect()->intended()
        : back()->withErrors([...]);
}

Turn it on in observing mode. Decide later.

Add a site from your srvAudit portal and you'll have a server key and an integration snippet in under a minute. Nothing is ever blocked until you look at the numbers and choose to enforce.