tl;dr: The code should verify the user's second factor before the user's password.
Consider this, scenario A:
1. When attacker enters a username and bad password. then they receive a bad password error.
2. When attacker enters a username and good password, then they receive a 2FA prompt.
And then scenario B:
1. When attacker enters a username and bad password, then they receive a 2FA prompt.
2. When attacker enters a username and good password, then they receive a 2FA prompt.
In scenario A, the website leaks password validity to the attacker. In the case of a brute force attack, the attacker can use the 2FA prompt as a signal that they found a good password. Scenario B does not leak that information, because the second factor was wrong or missing.
More concretely, this pseudo-code:
if user.authenticate_with_password(password)
if user.authenticate_with_second_factor(code)
# ...
else
raise InvalidSecondFactorError
end
else
raise InvalidPasswordError
end
Should instead be this pseudo-code:
if user.authenticate_with_second_factor(code)
if user.authenticate_with_password(password)
# ...
else
raise InvalidPasswordError
end
else
raise InvalidSecondFactorError
end
If MFA can be configured using myriad choices, should a user be prompted to "Insert security key" or "Input security code" or "Send code to your email/SMS" or "Tap YES on your mobile device"?
Since you can't know a priori what the second factor will look like, I'd say it's troublesome to try and present a challenge to every user regardless of their MFA configuration.
If your 2FA options all require the user to enter a code, you can simply display a "Please enter your 2FA code" dialog without divulging what kind of 2FA the user has.
How would you prevent someone from spamming a user just by knowing their username? Say, if the 2FA is done by SMS, or email.
An attacker brute-forcing the password could flood the user with multiple messages. The usual response is doing a password reset, but that wouldn't work in your system.
I wonder how systems that use magic links handle this.
In my pseudo-code example, we're raising a couple errors, InvalidSecondFactorError and InvalidPasswordError. You could imagine there could be finer grained errors, such as TotpRequiredError or HardwareKeyRequiredError, depending on the user's second factors, which could then propagate down to the UI via specific error codes.
The UI could then use these error codes to display the correct prompt, and then resend the request with the appropriate second factor.
You would have to randomize the error when the wrong password is inputed and ensure that for a particular username the returned error is invariant. Else an attacker could infer that when you get a different error you have a correct password.
It sounds good for stopping attackers, but if I am the real user and enter a bad password it is going to be pretty infuriating spending time troubleshooting the 2FA not working problem that doesn't actually exist. I suspect your service will get a reputation for completely unreliable 2FA which may have unintended consequences.
This can be solved with an error message at the end with something like "You either provided an incorrect password or your 2FA code is incorrect. Check and try again". This still ensures that someone is not able to guess the correct password and reuse it somewhere else where 2FA may not be enabled.
If you input a username and wrong password, in some cases, the service won't prompt you for your 2FA code.
If you input the right username and password, it will then go forward in the flow and prompt you for the 2FA.
I believe parent comment is suggesting the system should prompt for 2FA even if the password was incorrect, so that you can't infer whether you guessed the correct password without also compromising the 2FA method.
This only matters if you re-use passwords, though.
Well, doesn't it also matter if the 2FA method sucks? For example, maybe you can use a SIM swap to get the one-time code, but if you don't have the password, too, then that doesn't help you. In the above scenario, they can figure out whether they have the password or not, and once they do, then use a SIM swap to get the second factor (or whatever), and then they're in. If the login never tells them which factor is bad, it's a bit harder, right?
Consider this, scenario A:
1. When attacker enters a username and bad password. then they receive a bad password error.
2. When attacker enters a username and good password, then they receive a 2FA prompt.
And then scenario B:
1. When attacker enters a username and bad password, then they receive a 2FA prompt.
2. When attacker enters a username and good password, then they receive a 2FA prompt.
In scenario A, the website leaks password validity to the attacker. In the case of a brute force attack, the attacker can use the 2FA prompt as a signal that they found a good password. Scenario B does not leak that information, because the second factor was wrong or missing.
More concretely, this pseudo-code:
Should instead be this pseudo-code: Hope that makes sense. :)