On the other hand, if the password is hashed client side, we KNOW that it cannot be stored as plaintext, because the server never knows it.
1. Bcrypt(or scrypt, or similar) for initial hash of password. (used PBKDF2 i think before). 2. Add time expiration to the challenge-response 3. Rate limit the challenge/response per account 4. make sure to use a good hash for HMAC() (was using sha-1 since JS was much slower then).
Ideally I'd also have a single challenge/response token per user and not let you get several to try against (i.e. invalidate old ones) but I'm not sure how I'd make that scale well. Maybe a salt+counter put into a hash (like HOTP) and then after a token is used invalidate all tokens with the counter less than the most recently attempted version?