Fraud and identity

Protect mobile apps against credential stuffing attacks

Where to stop credential stuffing against a mobile app: telling bots from real clients, splitting decisions across app, gateway and backend.

Credential stuffing is the automated replay of username and password pairs leaked from other services against your login. Against a mobile app, the bots rarely run your app at all: they read its traffic once, then call the login API directly from scripts at scale. Protecting a mobile app therefore means answering one question on every login request: did this come from a genuine copy of the app on a real device, and does the pattern look human? This article explains how to separate hostile automation from legitimate clients, where each decision belongs, and which policies keep real users signed in during an attack.

The abuse path: three kinds of client

Every login request that reaches your API comes from one of three places, and a good defence treats them differently.

  1. Automated requests. Scripts or bot frameworks call the login endpoint with leaked pairs, rotating IP addresses and device identifiers to look like many users.
  2. Invalid clients. A modified copy of your app, an emulator farm or an instrumented app driven by a hooking framework. The request looks like it came from your app because, in part, it did.
  3. Legitimate automation. Password managers filling fields, users with accessibility tools, your own test automation, and partners you have allowed to call the API.
  1. Collect leaked pairs

    Combination lists from unrelated breaches are loaded into a bot tool.

  2. Learn the API

    The attacker records the app's login call once, from a real or emulated device.

  3. Replay at scale

    Scripts send the call with thousands of pairs through rotating addresses.

  4. Require app evidence

    Requests without valid evidence from a genuine app are refused at the edge.

  5. Decide per account

    The backend weighs evidence, velocity and account history before allowing a login.

How a credential stuffing run against a mobile login usually unfolds, and where evidence changes the outcome.

The distinction between the first two groups matters. Pure scripts are refused once the API demands evidence that only a genuine app on a real device can produce. Invalid clients are harder: they run your code, so the evidence must also say whether that code was modified, instrumented or running in an emulator.

Hostile automation

  • No app evidence, or evidence replayed from another session
  • Emulator, hooking framework or modified app copy
  • Many accounts from one device profile
  • Uniform timing, no screen interaction

Legitimate automation

  • Evidence from a genuine app on a real device
  • Password manager or accessibility service filling fields
  • One user, a small number of devices
  • Test and partner traffic from known, separately authenticated clients
Legitimate automation needs its own path. Partner and test traffic should authenticate differently, not hide among app users.

Otukan attaches app integrity and runtime evidence to login requests from Android and iOS apps, and detects emulators, hooking frameworks and modified copies used to automate logins. Each platform produces its own evidence: Android offers the Play Integrity API and iOS offers App Attest, and Otukan treats them as separate sources.

OAT-008

OWASP automated threat identifier for credential stuffing

OWASP Automated Threats to Web Applications

M3

OWASP Mobile Top 10 (2024): insecure authentication and authorisation

OWASP Mobile Top 10

SP 800-63B

NIST guideline asking verifiers to check new passwords against compromised lists

NIST

429

HTTP status for too many requests, defined in RFC 6585

IETF RFC 6585

The enforcement boundary: app, gateway and backend

A common mistake is to ask one layer to do everything. The app cannot see traffic from scripts that never run it. The gateway sees volume but not accounts. The backend knows accounts but receives requests only after the gateway has let them through. Each layer should make the decisions it is best placed to make.

DecisionAppGatewayBackend
Produce evidence that the request came from a genuine appYes
Report emulator, hooking or modified app signalsYes
Refuse requests with missing or invalid evidenceYesYes
Rate limit by address, device and endpointYes
Rate limit failed logins per accountYes
Check passwords against known breached listsYes
Step up with a second factor or challengeYesYes
Lock, notify or reset an accountYes
Decisions per layer in the table above
Backend5App3Gateway2

Counts come from the enforcement table on this page. They show where the work sits, not how much traffic each layer stops.

The app’s role is narrow but essential: it is the only place that can produce evidence. That evidence is worth little unless something checks it, which is why the gateway and backend should both be able to refuse requests without it. For the gateway pattern in more depth, see WAF and API gateway signals.

Continuity: rate, challenge, retry and failure policies

Credential stuffing defences fail in two ways: they let the attack through, or they lock out real users. The second is easier to miss, because it looks like a working defence. Write the policies down before an attack, test them, and make sure support teams know them.

PolicyWhat to decideExample starting point
RateLimits per address, per device, per account and per endpointTight per account, looser per address to allow shared networks
ChallengeWhen to ask for a second factor or a challengeNew device plus weak evidence, or unusual velocity
RetryHow the app backs off after a refusalHonour the server’s retry header; never retry login automatically
FailureWhat happens if the evidence service is unavailableFail open with extra scrutiny for known users, fail closed for new devices

The retry policy deserves special attention. An app that retries failed logins on its own adds to the load during an attack and can trigger the same limits you set for bots. A standard response lets the app behave well:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 120

{"error": "rate_limited", "retry_after_seconds": 120}

For the protection itself, see credential stuffing protection.

Before the next attack

  • Confirm that the login API refuses requests with no app evidence, on both Android and iOS.
  • Give partner and test automation their own credentials and route, separate from app users.
  • Set rate limits per account as well as per address, and test them from a shared network.
  • Decide the fail-open or fail-closed rule for when the evidence service is unavailable.
  • Make sure the app honours retry headers and never retries a login on its own.
  • Check new and changed passwords against a breached-password list.

Common questions

  • Does protecting the app itself stop credential stuffing?

    Not on its own. Most bots never run your app: they record its login call once and replay it from scripts. The defence starts at the API, which should refuse login requests that carry no valid evidence from a genuine app on a real device, with the gateway and backend making the remaining decisions.

  • What does Otukan add to a login request?

    Otukan attaches app integrity and runtime evidence to login requests from Android and iOS apps, and detects emulators, hooking frameworks and modified app copies used to automate logins. Android offers the Play Integrity API and iOS offers App Attest, and Otukan treats them as separate sources. Account decisions stay with your backend.

  • Which layer should handle each part of the defence?

    Each layer does what it is best placed to do. The app produces evidence and reports emulator, hooking or modified app signals. The gateway refuses requests with missing evidence and rate limits by address, device and endpoint. The backend limits failed logins per account, checks breached passwords and decides on challenges, locks and resets.

  • How do we avoid locking out real users during an attack?

    Write rate, challenge, retry and failure policies down before an attack and test them. Limit tightly per account but more loosely per address to allow shared networks, prefer a challenge or slower path over a hard lock, and make sure the app honours retry headers and never retries a login on its own.

  • How should partner and test automation be handled?

    Give it its own path. Partner and test traffic should come from known clients with their own credentials and route, separate from app users, rather than hiding among them. Password managers and accessibility tools are legitimate automation too: they fill fields in a genuine app on a real device used by one person.

Keep reading

Discuss your automated login abuse requirements

Tell us your platforms, framework and how your login API is exposed today. We never ask for binaries, signing keys, credentials or production data.

Or request a technical demo