Implementing user authentication is safe without relying on a framework (or a third-party library such as OpenID) to do this for you and not a trivial undertaking.
When looking at 10,000 feet, you need to decide:
- Do you have usernames, email addresses, or user IDs as your primary selector?
- How to store passwords? PROTIP:
password_hash() or scrypt is the way to go. - How should you handle the Remember Me checkboxes? There are many bad strategies on the Internet. Be skeptical of each of them, because they can introduce vulnerabilities into your application.
- How should an application handle users who have forgotten their password?
The information contained in this answer is current and relevant as of May 9, 2015 and may be outdated after concluding a password hash
Primary selectors
In general, usernames and email addresses are better than ID numbers.
There should not be any security requirements to keep usernames a secret, because in practice they will leak out when someone tries to register anyway.
You can decide whether to consider email addresses as secret. Usually users are not exposed to spammers, scammers and trolls.
Password hashing
You should use password_hash() and password_verify() if you are not experienced enough in writing cryptographic libraries to go up and on.
Beyond bcrypt
Sometimes developers like to create creativity (for example, add “pepper”, which usually means pre-hashing or HMACing passwords with a static key) and goes beyond standard implementations. We did it ourselves, but very conservatively.
For our internal projects (which have a much higher level of security than most user blogs), we wrote a wrapper around this API called PasswordLock , which first hashes the password with sha256 , then base64 encodes the original hash output, then transfers this hash to the hash base64 code in password_hash() and finally encrypts the bcrypt hash code with a properly implemented encryption library .
To repeat, instead of pepper, we encrypt password hashes. This gives us great flexibility in the event of a leak (we can decrypt and then encrypt again, because we know the key). In addition, we can run our web server and database on separate equipment in one data center to reduce the impact of SQL injection vulnerability. (To start cracking hashes, you need an AES key. You cannot get it from the database, even if you are running into the file system.)
// Storage: $stored = \ParagonIE\PasswordLock\PasswordLock::hashAndEncrypt($password, $aesKey); // Verification: if (\ParagonIE\PasswordLock\PasswordLock::decryptAndVerify($password, $stored, $aesKey)) { // Authenticated! }
Password vault with PasswordLock :
hash('sha256', $password, true);base64_encode($step1);password_hash($step2, PASSWORD_DEFAULT);Crypto::encrypt($step3, $secretKey);
Password Verification with PasswordLock :
Crypto::decrypt($ciphertext, $secretKey);hash('sha256', $password, true);base64_encode($step2);password_verify($step3, $step1);
additional literature