A second factor protects the account, but it does not stop the attacker from learning which passwords are valid. That distinction decides where the control belongs.
First the attacker gets the material: username and password pairs from breaches of other services, circulating in aggregated and cleaned-up lists.
Then the replay against your login endpoint is automated and spread across many IP addresses so that none crosses your blocking threshold. A high hit rate is not needed: it is enough that a small fraction of your users reuse passwords.
Finally the working credentials are separated out. That validated list is the product: exploited later, or sold on.
A second factor is requested after the password has been accepted. At that moment the attacker already has what this phase was for: the knowledge that the credential is valid. Failing to complete the login does not stop them recording the pair as good.
There are two side effects that get underestimated. The first is cost: if your second factor is a text message, every attempt with a correct password fires a message you pay for, and there is a specific fraud built on exactly that, known as SMS pumping. The second is notification fatigue: bombarding a user with confirmation prompts eventually gets one approved.
A control that acts before the password is validated, and that does not rely on a signal the attacker can imitate. That is where client authentication fits: if your login endpoint only accepts connections from registered installs of your app, the attack has no way in, because its client has no certificate to present.
ShieldCert is in closed beta. Request access and we will set it up with you.