Authentication

0. Never store users' passwords. Store "hashes" instead.

End users' passwords should never be stored in cleartext. There are no exceptions to this rule. Instead, store a "hash" generated by Bcrypt or another similar library.

Use Bcrypt to generate a "hash" of the user's password and store this in the database. When the user submits a password for authentication, use Bcrypt to verify that submitted password matches the stored hash.

The main problem with storing users' password in cleartext is not even that this will allow someone to steal the password and perhaps login into your application. Rather, it is the fact that the user may well be using the same password for all sorts of other applications. Maybe they are using it to log into their bank. For this reason, storing users' passwords in cleartext is never the right solution.

In most cases the best solution is to use a key derivation function (KDF) to generate what is colloquially referred to as a "hash" of the password. This "hash" is stored instead of the password used for checking if the password supplied by the user is correct. A key produced by a KDF is often referred to as a "hash" because KDFs are often based on cryptographic hash functions and are conceptually similar. Hash functions such as MD5, SHA1, SHA256, etc, are often used to implement "poor man's" KDFs. A simple hash function does not make the best KDF, however.

Proper commonly-used KDFs include Bcrypt, PBKDF2-SHA256, and Scrypt. We usually use Bcrypt. Note that PBKDF2-SHA256 is based on SHA256, but it's not the same thing as just using SHA256.

When using a KDF, it's important to use a different salt for every password to protect against dictionary attacks. If you were to try to implement your own KDF based on a hash function, you would need to make sure you use this salt properly. However, you shouldn't write your own KDFs. Use a standard one, and it will handle salts for you.

Bcrypt has one convenient feature that makes it easy to use well. Bcrypt can generate an appropriate salt automatically and you can tweak the strength by changing the "work factor" parameter. The salt and the chosen work factor are both stored in the resulting hash, so you don't need to worry about storing them separately. This also makes it easy to upgrade the strength of your "hashes" later, as you can have the users do this whenever they change passwords.

In rare cases you will need to be able to retrieve the original password. In those cases you will need to encrypt it and worry about how you manage the keys.

1. Do not recover passwords, reset them.

Storing a hash means that user's original password cannot be recovered. Consider this a feature. However, offer the user a way of resetting their password if they have forgotten it.

Password reset by email is a standard solution for password reset. You send the user a link with a token that allows them to set a new password. This solution should be implemented in such a way that the token can only be used once and that it would expire in a short period of time.

The token should be chosen to be hard to guess, for example by a cryptographically secure random generator (for example, NodeJS's crypto.randomBytes()).

2. Ensure minimum password quality.

The needed strength of the password depends on the application. However, it is important to require some minimal strength.

You probably shouldn't allow your users to use "123456" or "password" as their password.

3. Count failed login attempts.

Your server should not let the user keep trying different username and password combinations until they find a match. You should either lock them out after some time or use captcha.

This needs to be done on the server. A user who is trying to brute-force a login is not going to do it using your client code. They'll probably be using curl or something like this.

Your server should respond to this by blocking the user, either permanently or for a short period of time. A short lockout (say, 5 minutes) can be fairly effective in stopping a brute force attack. You can also require captcha at this point.

If you lock the user out permanently, you will need to make sure there is a way for them to reinstate their account.

4. Consider "social" authentication.

Authentication using Google, Facebook, etc., allows you to push some of the work onto those applications and the user has one less password to remember.

5. Use token-based authentication.

Once the user is authenticated, you will need to either issue them an authentication token or a cookie. Tokens are vastly preferred.

Cookies are easier to steal and are hard to use in a multi-server setup. Tokens solve both of those problems. They can also be used with a completely stateless server architecture.

Our standard solution is JWT-style bearer tokens.

6. Consider how you store the tokens.

If you want the user to stay logged in after they close your application window, you will need to store the token somehow on the client. localStorage is a common option and is suitable for most applications, but you need to consider the specific requirements of your app. You need to remember to remove the tokens upon logout.

Tokens stored in localStorage can be accessed by any JavaScript loaded from the same domain. This becomes a problem if your application is vulnerable to an XSS attack (see XSS). Some applications may require a more elaborate approach to storing authentication tokens, though it's important to remember that there is no silver bullet here.

There are two routes towards securing localStorage further. One is to encrypt the token before storing. This step creates a hurdle for an attacker, but you now face the challenge of where to store the key. There are a few options, none of them entirely satisfying.

Another approach is to take advantage of the fact that localStorage is domain specific to ensure that the token is written by a domain that we are certain is safe from XSS. In this case we need to figure out how to move the token between the domains, which can be done using an iframe and postMessage().

7. Consider the personal information stored in the token.

When using JWT tokens, consider that the token carries a data payload that is signed but is not encrypted. If the content of that payload is sensitive, the server should be encrypting it before sending it to the client.

A JWT token is encoded and looks encrypted, but it can be trivially converted into a JSON object.

8. When using cookies, protect against CSRF.

If you must use cookies, make sure your app is safe against cross-site request forgery (CSRF or XSRF). But really, just avoid cookies.

CSRF is an attack in which someone causes the user's browser to call your server using that user's credentials. In this case, the user is someone who have authenticated with your server using your application, but the call is being made by another application on that user's computer.

This can involve making an action or getting data. Requests for JSON content are in theory protected from CSRF by the same-origin policy, but in practice this can be circumvented. The JSON Array" vulnerability is just one of the ways this can be done.

Angular provides a partial solution for this via the use of X-XSRF-TOKEN header. However, in order for this to work this needs to be supported on the server. You would also need to verify that this is actually working in your case.

If implementing CSRF in Node.js, beware of this vulnerability in methodOverride middleware: "Bypass Connect CSRF protection by abusing methodOverride Middleware."

And again, ask yourself if you really want to be using cookies.

9. Pick a sensible session expiration policy.

When you authenticated the user, they should not stay authenticated forever. The specific policy that makes most sense depends on the application.

A common strategy is to set an expiration time and provide an endpoint for renewing the token. In this case, the client should keep track of whether the user is still active and renew the token prior to its expiration.

For example, if the server issued a token that lasts for 1 week, the client wait for 3 days then renew the token when the user interacts with the system again.

10. Require re-authentication for sensitive features.

It is generally a good idea to ask the users to re-authenticate before accessing features that may be particularly sensitive. Among other things, the user should always be asked to re-authenticate before changing their password.

If the user is resetting their password because they forgot it, they would need to use an alternative way of establishing their identity, e.g., the password reset token that was sent to them by email (see §8.1.1).

This becomes particularly important when using an authentication strategy that allows the user to stay logged in indefinitely.

HTTP does not provide standard vocabulary for such a re-authentication request, but this can be communicated by a simple 401, as long as the client knows how to handle it.

11. Consider multi-factor authentication.

Multi-factor authentication means asking the user to identify themselves with something they have in addition to providing the password. The most common solution today is having the user enter a code that you send to their phone.