There are thousands of different tutorials on the internet. And this is the way a programmer can learn something. As we wrote in our article about courses on programming, the main plus when you are a developer is in free and available online resources to study. But not all of them have the up-to-date and correct information. So pay attention and better compare the information from several resources. Today we are going to tell you about Node.js authentication tutorial mistakes you can face.
Browsing through tutorials we’ve found that most of them unfortunately incomplete or wrong in some ways. Sometimes they are related to a security mistake that will potentially hurt the users. In this article, based on the Your Node.js authentication tutorial is (probably) wrong material by Micaksica, we will show you some common authentication pitfalls the tutorials don’t show.
That is really a pity, that there are a lot of tutorials that specialize in setting up Passport for your Node.js application, and nearly all of them are wrong in some way or another, and none properly implement the full stack necessary for a working web application.
To make Node/Express a safer ecosystem for new developers we brought out some common authentication tutorial mistakes you should avoid.
- Credential storage
Storing and recalling credentials is pretty standard fare for identity management, and the traditional way to do this is in your own database or application. The passport that identifies the level of user safe awareness, requires the passport-local module for handling password storage in your own database, written by the same developer as Passport.js itself.
Let’s remember a great cheat sheet for password storage by OWASP, which boils down to “store high-entropy passwords with unique salts and one-way adaptive cost functions”.
As a Passport user, your first place to look will be the example of code for passport-local itself, which gives you a sample application you can clone and extend. But if you just copypasta this, you will have not too much, as there’s no database support in the example and it assumes you’re just using some set accounts. The passwords for the example aren’t hashed in any way and stored in plaintext right alongside the validation logic in this example. Credential storage isn’t even considered.
The other example of the tutorial we found was better, with brypt with a cost factor of 10 for password hashing. The top result on Google, the tutorial from scotch.io, also uses bcrypt with a lesser cost factor of 8. Both of these are small, but 8 is really small. Most bcrypt libraries these days use 12. The cost factor of 8 was for administrator accounts eighteen years ago when the original bcrypt paper was released.
Password storage aside, neither of these tutorials implement the password reset functionality, which is left as an exercise to the developer and comes with its own pitfalls.
- Password reset
None of the top basic tutorials explain how to do password reset at all with Passport.
The most common ways people get the password reset wrong are:
– Predictable tokens. Tokens that are based upon the current time are a good example. Tokens made by bad pseudorandom number generators are less obvious.
– Bad storage. Storing unencrypted password reset tokens in your database means that if the database is compromised, those tokens are effectively plaintext passwords. Generating a long token with a cryptographically secure random number generator stops remote brute force attacks on reset tokens, but it doesn’t stop local attacks. Reset tokens are credentials and should be treated as such.
– No token expiry. Not expiring your tokens gives attackers more time to exploit the reset window.
– No secondary data verification. Security questions are the data verification for a reset. Of course, then the developer has to choose good security questions. Security questions have their own problems. While this may seem like security overkill, the email address is something you have, not something you know, and conflates the authentication factors. Your email address becomes the key to every account that just sends a reset token to email.If you’re new all of this, try OWASP’s Password Reset Cheat Sheet. Let’s get back to what the Node world has to offer for us on this.
We’ll divert to npm for a second and look for password reset, to see if anyone’s made this. There’s a five-year-old package from the (generally awesome) substack. On the Node.js timeline this module is jurassic, and if you wanted to nitpick, Math.random() is predictable in V8, so it shouldn’t be used for token generation. Also, it doesn’t use Passport, so we’ll move on.
Stack Overflow isn’t of too much help, as startups love plugging their IaaS everywhere. We’ll take the first result for the Google search express passport password reset. Here is bcrypt again, with an even smaller cost factor of 5 used in the text, which is far too small of a cost factor for modern use.
However, this tutorial is pretty solid compared to others in that it uses crypto.randomBytes to generate truly random tokens, and expires them if they aren’t used.
- API tokens
API tokens are credentials. They are just as sensitive as passwords or reset tokens. Most developers know this and try to hold their AWS keys, Twitter secrets, etc. close to their chest, however, this doesn’t seem to transfer into the code being authored.
Let’s use JSON Web Tokens for API credentials. Having a stateless, claimable token is better than the old API key/secret pattern that has been used for the better part of a decade. Perhaps your junior Node.js dev has heard of JWT somewhere before, or saw passport-jwt and decided to implement the JWT storage.
For example, one of the tutorials on User Authentication using JWT (JSON Web Token) in Node.js has such mistakes in credential storage:
– We’ll store the JWT key in plaintext in the repository.
– We’ll use a symmetric cipher to store passwords. This means that you can get the encryption key and decrypt all of the passwords in event of a breach. The encryption key is shared with the JWT secret.
– We’ll use AES-256-CTR for password storage. We shouldn’t be using AES to start, and this mode of operation doesn’t help. We’re not sure why this mode specifically was chosen, but the choice alone leaves the ciphertext malleable.Note that JSON Web Tokens are signed, but not encrypted. That means that big blob between the two periods is a Base64-encoded object.
What about the next tutorial? The next tutorial, Express, Passport and JSON Web Token (jwt) Authentication for Beginners, contains the same information disclosure vulnerability…
- Rate limiting
There is no mention of rate limiting or account locking in any of these authentication tutorials. Without rate limiting, an adversary can perform online dictionary attacks in which some tool can gain access to an account with a weak password. Account lockout also helps with this problem by requiring extended login information from a user the next time they log in.
Remember, rate limiting also helps availability. bcrypt is a CPU-intensive function, and without rate limiting functions using bcrypt become an application-level denial of service vector, especially at high work factors. Multiple requests for user registration or login password checking are an easy way to turn a lightweight HTTP request into costly time for your server.
There are tons of rate limiting middlewares for Express, such as express-rate-limit, express-limiter, and express-brute. You can run a reverse proxy in production and allow rate limiting to requests to be handled by Nginx or whatever your load balancer is.
The conclusion tells us that if you are a beginner don’t give the tutorials 100% of trust. Copying from tutorials will likely get you in authentication trouble in the Node.js world.
And if you are an experienced developer, you shouldn’t allow yourself to blind-follow the instructions.
More examples and hints you can find in the original article by Micaksica we mentioned above.
Subscribe to our weekly newsletter to find more interesting information!