OK good to know you have at least some experience. I'll tell you how I generally do it, but I would encourage other members of this board to give their input on it. I'll also tell you where it needs work.
My login starts with the form, the user enters the username and password. In this form I have 2 hidden inputs, one is called 'challenge' and one is called 'encrypted_password'.
The challenge field holds a 255 character long random string, generated by PHP and stored in the users $SESSION. The key of this is having 2 instances -- one server side ($SESSION) and one local (browser). The challenge is reset and recreated everytime the login form is shown. The challenge is a key value, increasing security. I'll elaborate in a minute.
Also when the login form is shown, PHP will set the current IP ($SERVER['REMOTE_ADDR']) in the $SESSION. This can be verified later on to ensure the same IP is using the challenge that is created. If anyone would ever intercept, the IP check would have a good chance of throwing them off.
When the user clicks submit, a javascript function is called (onsubmit) that collects the password and empties the password field. The password then gets encrypted through sha1. The function then uses the sha1 algorithm to create a hash of "challenge:password" (replace challenge with the challenge and the password with the encrypted password)(yes that means the encrypted password gets encrypted again with the challenge attached to it). This value will then be stored in the hidden field "encrypted_password". The form now consists of the username, the challenge, an empty password field, and the hidden field encrypted_password with the sha1 encrypted string. This way, when the user submits, only the username will be readable for third parties in the $_POST parameters.
Serverside, you receive the challenge, username and encrypted password. First check the IP in the $SESSION with the $SERVER['REMOTE_ADDR'] var. If this does not match, this means the user has changed internet connection (happens with laptops, unfortunately, lets say you open the login form at work, take the laptop home, connect there, fill it in and post the form, then your IP has changed. doesn't happen too often though). If IP is different, you can choose what you want to do. You can throw a simple error 'username/pass does not match', the user will think it has been a typo. 2nd try the IP is reset and the user will login correctly. If it's a hacker, then who cares what error message he gets.
To save yourself some issues, first try to find the database record that matches the username. No record? then username does not exist and you can show the error to the user. Note that I generally do not recommend explicit errors, do not let the user know the username does not exist or any other details that would make it easier for a hacker to find a username/pass. Just tell them credentials are not correct.
If you do find a record, get the password from the record. This should be sha1 encrypted as well, so if anyone hacks into your database, the passwords are not readable by anyone. Some people tend to use the same login credentials for everything, which would cause a severe threat to them if passwords are stored readable. Then if you have the password, get the challenge from the session and perform the same sha1 trick: sha1($_SESSION['challenge'].':'.$password_from_db);. The value that comes from this should be identical to the encrypted_password you get from POST. If it's not, tell the user credentials are incorrect.
When doing password retrieval you have an 'issue'. You cannot retrieve the password, because it's encrypted in the database. Instead, you'll have to create a new password for them and send this to them through mail. They should be able to change it afterwards.
This pretty much sums up my login process. A small issue lies in the requirement of javascript. The advantage however is that this is a user protection function, as it will prevent raw details being sent through post. the absence of javascript (some people disable it) would mean encrypted_password remains empty and the password field is sent through POST with the actual readable password. I would suggest to create the encrypted_password value on the server, so the rest of the script can remain the same.
Javascript does not have a native sha1 encryption function, but there has been a file around. Maybe if you google for hex_sha1() you might find the file. This is the function I use.
The userID would be the primary key (usually ID) of the record found with the username. Username should be unique of course. You can link all user-related database records by storing the userID value in a table column of that record. I set the userID in a session variable. Also I check the IP from the session with the user's IP on every request. I also instantiate a user object at every request, sending in the userID as argument. This then allows me a very automatic verification of the existence of the user (simple check on db record status/existence).
There's a small chance I missed something, as I'm doing this from the back of my head. Let me know if you find any issues or need any clarification on anything.