One of the worst possible things you can do as a developer is store a password in plain text. This means that when you take a password from a form you never want it to be stored so that someone can easily see it. Imagine if someone gets access to your database and can see every single password for all of your users. On top of that, most users have the same password for multiple sites so a password on one site can very possibly be the same for many other ones. So long story short - NEVER STORE PASSWORDS IN PLAIN TEXT!
So how do we store passwords? We encrypt them. Before we talk about how that's done, let's examine different forms of encryption.
-
Two way encryption - think of something like gibberish or pig latin or even a simple code that you made up with a friend to send a secret message. The important thing here is that both parties know how to decipher a message. This is easy to use, but imagine if someone else gets access to the key or knows how to decipher the code. This is why we don't use two way encryption for passwords. The only person who should ever know their password is the person who created it, so we need a different way, so we use one way encryption
-
One way encryption - this is how we store passwords. This method also known as hashing and it only allows text to be deciphered only by the person who knows the original text. For a password this is perfect. As developers we should never know or be able to figure out our user's passwords. If they for some reason forget it, we just send them a link to reset the password - we never want to just give them their password back in plain text.
The tool we use to hash passwords is called bcrypt. Bcrypt is a module based on the blowfish cipher. To install it we use npm install --save bcrypt
and make sure to add bcrypt = require("bcrypt")
when we want to use it in our code. Bcrypt provides functions for hashing, salting and comparing passwords.
How it works:
- Generate a random salt (A "work" factor has been pre-configured.)
- Collect a password.
- Derive an encryption key from the password using the salt and cost factor. Use it to encrypt a well-known string.
- Store the cost, salt, and cipher text. Because these three elements have a known length, it's easy to concatenate them and store them in a single field, yet be able to split them apart later.
When someone tries to authenticate, retrieve the stored cost and salt. Derive a key from the input password. Encrypt the same well-known string. If the generated cipher text matches the stored cipher text, the password is a match.
Stored in the database, a bcrypt "hash" might look something like this:
$2a$10$vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa
- 2a identifies the bcrypt algorithm version that was used.
- 10 is the cost factor; 2^10 iterations of the key derivation function are used
- vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa is the salt and the cipher text, concatenated and encoded in a modified Base-64. The first 22 characters decode to a 16-byte value for the salt. The remaining characters are cipher text to be compared for authentication.
- $ are used as delimiters for the header section of the hash.
The bcrypt utility does not maintain a list of salts. Rather, salts are generated randomly and appended to the output of the function so that they are remembered later on. Put another way, the "hash" generated by bcrypt is not just the hash. Rather, it is the hash and the salt concatenated.
Not only is it important to hash a password, we need to add an additional layer of security and we do that by adding salt. Salting provides an extra hash at the end of our password which makes it much much more difficult for someone to crack our password using brute force (trying again and again) or a lookup table.
You can read more about this here
If you STILL want to read more about bcrypt and salting, this is a fantastic article.
One of the most important concepts in building an application is authentication - the process of ensuring that our users are valid (actually exist in our database). We only want to grant access to certain pages for users who are not logged in. Imagine if you could access a banking website and see your account without logging in....that would be pretty disasterous because anyone would have access to it. By authenticating our users, we can make sure that only the right users have access to the right pages. Another example to think about would be if other facebook users could have access to your settings page...that would be quite bad.
Passport is authentication middleware for express. It comes with a variety of "strategies" which enable us to build authentication using our own information (this is known as the "local" strategy) like a username or a password as well as leveraging other forms of authentication like OAuth (which enable us to allow users to log in via twitter, facebook, google, instagram etc. The benefit of this is that we do not have to store usernames and passwords - we just leverage data from other sites that already have that information)
Before we talk about these terms, think of this story. You go out to a bar and before you enter, they check your ID to make sure you're over 21 and if you are, they stamp your hand and let you in.
The act of checking your ID and verifying your age is the process of authentication and very similar to what happens when a user tries to log in (we check their credentials just like the bartender checks your age).
Once the user is approved (bartender verifies you are over 21), your hand is stamped. Now anytime you go out and back into the bar, they will remember that you have been authenticated. This process of stamping your hand is what the Serialize function does. It is a one time thing that creates the hand stamp (or in the case of our web application, a session).
When you leave the bar and come back, the bartender checks your hand again to make sure you're authenticated. This is the process of deserialization. This happens on every page and is a very fast check to see if the user has been authenticated (or has that hand stamp). Once the user moves to another page, we will run the deserialize function again which checks if the session still exists and if the user is authenticated.
This module allows us to store a tiny piece of session information in a cookie. This is how we are able to maintain state if our server goes down. This module depends on cookie-parser so we need to not only run npm install --save cookie-session
but also npm install --save cookie-parser
. Serializing creates a session for us which is great for persisting accross pages. But when happens when our server goes down or we restart it? Without a cookie, we would have to force our user to log in again, which is not the best user experience - so in order to maintain state even when a server goes down, we use a cookie.
We use this express module to render messages to notify our user of any errors or successes (failure to login or a message after logging in)