Let’s make some notes to previous answers.
First, it’s probably not the best idea to use hash algorithms client side because if your password is salted on the server side, you won’t be able to compare hashes (at least not if you don’t store the client hash in the database in one of the hashing layers from the password, which is the same or worse). And you don’t want to implement the hashing algorithm used by the database on the client side, it would be silly.
Second, trading off cryptographic keys aren’t ideal either. The MITM could theoretically (considering he has a root cert installed on the client) change the cryptographic keys, and change with his own keys:
Example from an original connection (not considering TLS) from a theoretical server that exchanges keys:
Client request public keys > server holds the private keys, generate public keys to client > server sends public keys to client
Now, in a theoretical MITM atrack:
Client request public keys > MITM generates fake private keys > Server holds the private keys, generate public keys to client > MITM receives the public keys from the original server, now, we’re free to send our fake public keys to the client, and whenever a request comes from the client, we will decrypt the client data with the fake keys, change the payload (or read it) and encrypt with the original public keys > MITM sends fake public keys to client.
That’s the point of having trusted CA certificate in TLS, and that’s how you receive a message from the browser warning if the certificate isn’t valid.
In response to the OP: in my humble opinion you can’t do that, because sooner or later, someone will want to attack a user from your service and will try to break your protocol.
What you can do, however, is to implement 2FA to prevent people from ever trying to login with the same password. Beaware of replay attacks, though.
I’m not great with cryptography, please correct me if I’m wrong.