Following your lead, I'm just going to list my thoughts:
- Make sure the email address isn't already in use
- To be clear, the MD5 hashing algorithm doesn't add randomness to anything. As a result, it isn't really doing a whole lot for you here, other than making it not-immediately-obvious where your salt came from. I think it would be better to use a CSPRNG to generate your salt than basing it off of user data and running it through MD5.
- This is an application nitpick, but it sounds like you are creating the user record (step 2) and then coming back and storing the salt (step 4) and then storing the hashed password (step 8). There is no reason you can't just do all of that in one insert operation. You'll get better performance, but most importantly, your code will be simpler and easier to maintain.
- To be clear, you don't want to use an encryption method to hash your passwords. Hashing and encryption are two separate concepts with different preferred algorithms. Make sure you are hashing: not encrypting. From what you have written I think that you are on the right page, and are simply using the wrong terminology. It's an easy mistake to make, but the difference is critical enough that it is worth me pointing this out.
- There is no good reason to use two separate encryption algorithms. The only reason to do such a thing is to make it so that people can't try to brute force your passwords if they find your database. However, it is a security measure that relies on your source code being kept secret. Sometimes they both get leaked though. This step isn't useless, but it also isn't as useful as you might think, and it complicates your code. If you want a protection like this I would just include a pepper and stick to a single pass of a secure password hashing algorithm.
- On login, make sure you only ever return a single, non-descriptive error message to the user "Invalid username/password combination"
- Don't do a simple comparison of the hashed password of the user to what comes out of the database (i.e.
$hashed_input == $user_hash
). There are some subtle ways in which such a straight-forward comparison can be used by a malicious attacker to help crack passwords. Some languages have helper methods to facility password hash comparisons. Use one or find one.
There can be a large number of subtle ways for security bugs to creep in to the login system of an application, which can easily be missed in an overview like this. Considering that it is the first line of defense of your application, it is important to get user authentication right. As a result, your best bet is to use a well-tested and well-supported authentication module that already exists, rather than building your own. If you do decide to stick with writing your own, then you should at least post your code on codereview.stackexchange.net afterward.