In my answer, I'll largely reproduce / use what is written in "Method to Protect Passwords in Databases for Web Applications" by Scott Contini, you definitely should read at least section 4 for the actual guidance and the whole paper for the background and the whys.
The paper has a more distinguished approach than you, but in principle does the same. The problem with the salt is solved by storing a per-user salt in the database and upon request hash(username||domain||per-user-salt)
is computed by the server and served to the client as the salt.
Now for the procedure as outlined in section 4:
- Store the username, $H(s)$ and $v$ in your database upon registration / password change. $v$ is said per-user salt (which should be 128-bit random and regenerated upon password change), $H(s)$ is the fast hash of the slow hash.
The salt is as described above. Additionally store a system-level secret $\sigma$ of the same properties as $v$, regenerate it occasionally.
- The client (the app) gets the username from the user (or from its storage). It now sends a request with said username to the server.
- The server looks the user up. If the user does not exist, $\sigma$ replaces $v$ in the salt computation. The salt and the (system-wide) parameters of the slow hash are sent.
- The client now computes the slow hash using the password and the given parameters and sends the hash.
- The server hashes the received result. The user is accepted if and only if the user exists in the database (you may make a new query here) and the hashes (from the DB and the computation) match.
Oh and you may want to use a more modern hash-function than MD5 here, because if it comes out that you use MD5 people (who have heard that "MD5 is broken") will come to you and complain about why you use a broken hash function. This might cost you users, because not everybody is going to care about the (correct) explanation that the "broken-ness" of MD5 doesn't matter in this particular case. So rather use a PR-friendly hash function like SHA-256.