Developing a secure membership system can be complicated as there are many potential danger areas that you must be aware of. In this post I hope to cover all of the areas you need to think about and plan for.
Should I have users log in using their email address or use user names instead?
From a usability point of view I would recommend using email address as people are more likely to remember that than a user name. Personally I often forget user names to websites that I use frequently or infrequently, but I haven't ever forgotten my email address.
The one drawback to using email addresses is if more than one person share the same email address and want to have separate accounts. If you need to accommodate this then you need to use user names.
Should I require a minimum level of password complexity?
Depending on the type of application some level of complexity should be required. A banking site should require a high level of complexity such as combination of upper and lower case, at least one number, and at least one non alphanumeric character. A site that stores little or no personal information and does not involve payment may simply demand a minimum number of characters.
How should passwords be stored in the database?
Passwords should never be stored in plain text, nor should they be stored in a way that they can be retrieved (for example to send to the user). A one way hash is the way to go but many methods are easy to break so a modern, known-good method should be used.
What are the best practices for log in forms?
For privacy and security reasons you should not confirm that the email address has an associated account on the log in form. If the user enters a valid email address but the wrong password then you should provide a non-specific error message such as "Invalid email address or password".
How should password resets be handled?
A password reset system is a safer choice than simply emailing someone their password.
When a request is made you should not lock out the account. You don't want to give someone the ability to lock out someone else from their account.
The password reset request page should include a CAPTCHA to prevent automated attempts. This will protect somewhat against your mail server being possible forced to send a large volume of emails, possible to people who do not have accounts with you. Otherwise a competitor could use this feature to get your mail server black listed.
It is best to record all password reset requests in a separate database table, which should contain the date and time of the request, the email or account identifier (so we know which account to reset), and a unique identifier to use in the link to reset the password. This identifier should be complex and not largely based on date or time so as not easy to guess. You may also want to record the IP address for the request, although these can be faked so this should not be relied upon.
When a password request is received a record would be added to that table and an email sent to the email address with the link to reset.
Again, we don't want to disclose whether the email address has an account or not, except to the someone who has access to that email address. So when a password reset is requested you should tell the user that an email has been sent to that address with further instructions. If there is no valid account for that email address then you would tell the user in the email.
I suggest allowing multiple password requests to be able to be recorded and for all to be valid. Often a user may get impatient and make multiple reset requests. Although a little less secure it is better for the user experience.
However, each request should be valid only for a set period of time, which is suggested to be one hour. After this time the reset URL should be invalidated.
Because emails are not secure and someone could have inappropriate access to that email address you should implement at least one more level of security. This could be in the form of a security question. Some websites use a SMS to a mobile phone number as an extra security level, but given that many people have their email on their phone if the phone falls into the wrong hands then this method is not secure at all.
If using security questions, then the questions need to be well chosen. Questions should be chosen so that they have a large number of possible answers (eg. there is only a limited number of common answers for what is your favourite colour), are not easy to find out (what school you went to, mother's maiden name could be easy to find out for many people), require very specific answers (question should not be answerable in different ways by the same person), are constant (people may change their favourite things over time), and are not cultural specific. Questions about your first ... could be a good choice but there needs to be enough questions for a user to choose them as not everyone has had a first everything of course.
Once the password is changed you should invalidate the request, as this limits the amount of time an attacker could access the reset URL. You should also not automatically log the user in, as if someone has guessed a valid password reset link, at least they won't know what email address to use to login.
Once the password reset is complete you should send an email with notification of the password change to alert the user to the change (in case they didn't do it), but don't send the new password in the email.
How should forgotten user names be handled?
If you have users log in with a user name instead of email address then you need to have a system in place to retrieve a forgotten user name.
Similar to the password reset I would recommend a CAPTCHA protected page where the user can enter their email address. Once they submit you should display a message indicating further instructions will be provided in an email.
Although you don't want to disclose the email address associated with the user name, it is a good idea to give some clue as to what email address the user can expect it to be sent to. Yahoo do this quite well by partially showing the email address (eg. j*******s@******l.com).
You can then email them with a list of all user names associated with their email address, or a message saying there are none if that is the case.
Armed with their user name they can then log in or request a password reset if they have forgotten their password too.
What happens if the user no longer has access to the email address they registered with?
If a mobile phone number has been previously recorded the reset URL could be sent via SMS instead.
Otherwise you could require the user contact you (most likely by telephone) to prove their identity and then provide a new email address to which a reset email could be sent.
Should I use SSL?
All of the identity verification process and anywhere passwords or secret question answers are entered should only be done on secure pages (HTTPS).
What are the best practices for the registration page?
After registration you should send an email with a link to verify that the user has access to the email address they used. You should show a message saying that an email has been sent to their email address (show it so they can double-check it) and if they don't receive it within a reasonable amount of time then check their junk mail box or anti-spam service, if any.
Once the user has verified their address you should not automatically log them in. That way, if someone found a valid verification link they wouldn't know the email address associated with the link.
One risk with the registration page is that hackers could check for whether an email address has an account by trying to register under that email address. You can avoid this by instead of telling the user that the email address already exists on the registration form, send an email notifying them of this instead of the email validation message.
But what happens if a previous owner of this email address no longer uses their account and a new owner of the same address now wants to register? You don't want to invalidate the original account as this could mean that someone could invalidate someone else's account with just access to their email address. You also don't want to block the new owner from creating an account of their own.
To accommodate this you could allow for multiple accounts under the same email address but this would then be a problem if both had the same password.
A simple solution to this problem would be to use user names instead but this does go against what I said previously. Also users would only able to log in with their user name and not their email address.
A more complex solution is to make the new owner's account the active account for that email address once they have verified their email address, and flag the old owner in the database so that when they next log in they are prompted to confirm their email address. If they cannot they must change to a different address before they can get access again. And at that point? Then you do the same to the other account.
This doesn't take into account the possibility that people may share the same email address but want separate accounts. For this the only answer is using user names instead of email address.
Should I lock users out after multiple failed attempts?
Firstly, you should be aware that this could expose the validity of an account for that email address.
I would recommend that you create a database table for failed attempts and keep a log of the email address and date/time of the request. If there are more than a set number of attempts using that email address within a set time then the system should not allow any more requests.
You could also store the IP address and only allow a set number of requests for any email address within a set time, although as a user may have multiple email address that they might try as well as multiple passwords they may have used, this should be a bit more tolerant.
You could always add a CAPTCHA after a set number of attempts, although annoying to the user it does provide a higher level of security.
You should be aware though that the IP address can be faked.
To what degree you need to implement these recommendations depends on the level of personal information you record and the type of application. However, if you do not make efforts to protect passwords and user accounts then you could open yourself or your business up to legal action and penalties should your data or accounts be compromised.