Web Authentication
This note is about web authentication and registration.
Registration might not be required. Never force users to register so they can access basic information or download a single file. That is just annoying.
The best registration form is a short registration form. Usually you need two pieces of information: email and password. Email can be used as the username when logging in.
Use live validation and server side validation. Show input errors next to fields but double check them on server side.
Explain yourself. If you need unusual information about the user, the context should explain why you need it or you need to state why you need it. Most of these things should be written in your privacy policy but no one reads those.
- Email is used for account management and, if agreed to, marketing messages.
- Facebook integration will not automatically post messages on your profile.
- It is possible to permanently delete each profile you create.
All authentication actions should be in one place. Always provide registration action near the login action and reset password action. When user logs in, logout action should be in the same area.
Show currently logged in user visibly on the interface. User specific menu is usually close to navigational items on websites. Show a welcome messages.
Welcome Back, Ruksi!
Do not allow too rapid login attempts. Prevent rapid login attempts by utilizing exponential wait time growth. Log number of system-wide failed logins. Enforce a global delay if value grows. For example, timer for 0, 0, 0, 0, 1, 2, 4, 8, 16... minutes.
Avoid verifying humanity. Verify user if bots might cost you money. Verify email if you are going to send them newsletters.
Don't use CAPTCHA at login by default. Require CAPTCHA only if trying a password the third time.
Avoid using CAPTCHA at registration. Requiring email activation filters yours out most of the simplest bots. Google for CAPTCHA alternatives.
Protect the sensitive data while communicating. If system involves money, use SSL in login, registration and all traffic after that.
Login Session
Use session data and cookies:
- Login status should be stored in session data at the server-side.
- Cookies should be used to reference that data, both having
session_id
. - Cookies should have secure and HTTP only tags on.
- Cookies referencing to an invalid session should be replaced right away to prevent session fixation.
Never blindly trust JavaScript. JavaScript can have the login status but it must always be validated on the web server.
Hash your session IDs. If you store your session IDs, hash them or else anyone with the database access can login as anybody.
More about the data design.
Passwords
Require passwords with high entropy, not complex passwords. Short passwords are fine if they contain special characters. Long passwords are fine in any form. Easiest to implement as live validation meter on the form that turns green when the password is usable.
Creating high entropy (hard to guess) passwords:
- Use over 4 words to create a sentence.
- Use uncommon words.
- Use non-standard uPperAsing.
- Use flawed grammarez.
- Use non-obvious spaces .
- Use non-obviou1s number2s wit3h patter4n.
- Use non-o/bvious sy/mbols wit/h patt/ern.
Do not limit the password length. Password max length can be 1000 characters, no less.
Notify user on password change. Fast to notice that someone else is playing with your settings.
Never use secret questions. They are an ancient feature and they have been broken their whole existence. But you if still want a fall-back for losing both password and email, use phone number to send a verification code.
Never disable autocomplete for password field. Some people really want to save those passwords.
Storing Passwords
Never store passwords in plaintext. Use a one-way hash function when storing the passwords. A one-way hash function is a series of mathematical operations that transform some input into a sort of fingerprint called a hash or a digest. You cannot create the original password from a hash.
password -> hash
hunter2 -> f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7
Use mature password hashing functions. Never use wacky or brand new hash functions. Never use separate hashing algorithms in a row, that is just silly.
Use `scrypt` or `bcrypt`.
Make sure your hash function adds salt or add it yourself. A salt is a random sequence of bytes which are added to the password. It is meant to make precomputing short and common password hashes harder. Anyone who knows the hash for sw0rdf1sh!
, will not be able to match it against the salted hash. Salt should be per user and per password. Store salt with the hash in the database, you can even use the same field.
salt password
6zvz3ylalpkp03lua8r4yyzdoq7e2js2 + sw0rdf1sh!
Make sure your hash function adds stretching or add it yourself. Stretching is the act of hashing a password multiple times with the same hashing function. It is done to make the computation times longer, processing a single password should take around 0,2 seconds on modern computers so that brute forcing becomes inefficient.
Four iterations:
passw0rd -> hash1 -> hash2 -> hash3 -> hash4 (store to database)
Three years later, you notice that hashing is too fast, so you can:
hash4 -> hash5 -> hash6 (store to database)
And when the user logs:
passw0rd -> hash1 -> hash2 -> hash3 -> hash4 -> hash5 -> hash6
Consider adding pepper. Pepper is long random value that is stored separately of the database, e.g. the in source code. If one of the systems breaks, others still have their pepper. So, it is useful if you have separate application and database servers. It is usually best not to use pepper because the security gain is not huge compared to the added complexity.
// Use HMAC, MAC is not good enough.
$pepper = "really really long random string value stored in the source code";
$hash = brycpt($salt + $password);
$hmacHash = HMAC($hash, $pepper);
$bcryptHash = brycpt($hmacHash);
// Store $bcryptHash and $salt in the database.
Password Reset
If you take security seriously, do not allow password resets by email. When you allow password resets by email, your system becomes as safe as the user's email.
Provide mechanism to get lost passwords. Do not generate passwords, offer a reset link. On password reset, force user to insert a new password right away. Generated passwords might not be changed so the password lingers in inboxes of all owned devices. Reset URL should be valid for one reset.
Do not state that an email does not exist on password reset. If found, that email may become target of spearphishing and privacy is already blown. Attacker can make list of valid emails to target.
Systems Without Passwords
Why passwords are troublesome:
- Username and password are hard to remember.
- You can use password generator or key ring but then you cannot access the account with a new device.
- It is harder to type on mobile devices.
- You can always get into the account if you only know the email password.
You can only use email. Every time user needs to login, they give their email and a verification message is sent to their inbox. Sessions stays up until they log out. If another device accesses the same account, the previous login session becomes invalid.
Database example:
user
- user_id = generated uuid
- email
- registered = when did user register
- last_login = when did user login last time
user_login_attempt
- attempt_id = generated uuid
- ip = from what ip did the login come from
- email = what email was used to login
- occured = when did this happen
user_login
- login_id = generated uuid
- user_id = reference to user.user_id
- token = uuid that should be saved to session for access
- token_validity = 10 min time limit when user can access token with email