How to Authenticate From Scratch Using Node.JS and MongoDB
Understand the basics of how auth works, and make a basic system without passport or its alternatives.
There are a large number of libraries available to enable authentication in every framework and database system. And while some of those are fully capable of catering to any general authentication need, there can be situations in which we may want an even higher level of control over how our users sign in to our product and in such a case, we would like to do everything from scratch.
However, before we start, note that if your need is a basic one, it is much better to go for a library like passport.JS, which is constantly updated by people very knowledgeable in the field of web security. Following this article will help you understand the basics, but making your site safe from attacks is a different topic altogether.
Some Important Points
- The code given at every step contains the previous step’s code too. So the best approach to do this would be to understand everything with the article, and eventually use the code in the final step. However, if you wish to write the code side by side, that is also completely fine.
- It is assumed that you do possess basic knowledge of Node.JS and MongoDB. While I will be explaining everything as we go along, it is difficult to introduce Node and authentication in just 1 article.
That being said, let us move on to the aim of this article. However, before we jump to the code, let’s divide our problem into smaller subproblems which we can solve one by one, eventually solving everything.
- Registering a new user
- Logging a user in
- Logging a user out.
- Keeping the user logged in for a session.
The first 3 points here are fairly straight forward and their meaning does not require much of an explanation. However, to understand why we need the fourth point, I need you to think of when you log in to Facebook on your laptop. How does it work? Do you get logged out every time you refresh your page or move to some other page inside Facebook itself? Do you have to login again if you open Facebook in a new tab? No. That is what that point will help us do. It will help the browser remember if we logged in, and use that until the session expires.
We can now start solving the above points one by one. Before we do that though, let’s add a 0th step to the process.
0. Setting up the project and making a database where the user data will be stored.
Of course, we will need a database where the login details are used. Let us do that first.
Setting up the project and database
Note the following steps work on Mac and Linux systems by default. If you wish to run it on Windows, use Gitbash in place of the default PowerShell.
For the project setup, follow the below steps:
- Open Terminal on your system.
- Move to the directory where you want to create the project.
mkdir authDemo; cd authDemo.This creates a directory named ‘authDemo’ here and moves us into it.
npm init -y.This creates the mandatory package.json file in the directory, required for node.
npm i -S mongoose express bcrypt express-session. All the names after the -S are just packages that we need for our project. We are installing them.
touch app.js. This creates an app.js file where we will have all our code. Open this file in your favourite code editor.
Now our project has been set up. We can start setting up our database. Inside the app.js file, write the following code:
Let’s understand what we have done:
- Require statements: line 1 and 2 simply include the packages we will need into our file.
- mongoose.connect : This connects us to our database, which is currently named ‘myapp’.
- Schema: In mongo, we need to define a structure as to how the entries in our collection will look. This is the schema. Here, for a user, we have said that we will have a
username, which will be a string, and
hashedPw, which will be a string. Both these fields are necessary, i.e. cannot be empty. Why we are using hashedPw(hashed password) instead of just password will be clear later in the article.
- Model: In MongoDB, we use models to do all the work through code. Hence, we make that model in line 15, and store it in
- App.listen: This line tells our code to run the server on port 3000 when we run the node file by running
node app.json the terminal. Due to this, now we’ll be able to access the routes we are making on localhost:3000/
Okay, so now our database is also set up. Let’s move on to Step 1, i.e. Register.
Register A New User
To add the register route, change your app.js file to:
Let us go over the changed lines here:
- Line 3: We require a new module, bcrypt. This will be the hashing algorithm we will use. We’ll discuss more about hashing, but let’s understand all the other points first.
- Lines 22–30 : This is our POST route for registering a user. Here, we are hardcoding the values of username and password, which you would usually get from a form. Next, we generate the hashed password, which we come to later. Once we have both these things, we create a user with these details and add it to our database. We now return the user we just created as a response from the route.
Okay, so now let’s understand what hashing is, and why we have done that. If we try to understand hashing in complete detail here, it will take up a whole lot of time, and moreover would not be relevant in our very limited scenario. So hashing in short is encryption of your password. Why do we need that? Security, of course. I know I mentioned we will not be dealing with security, but hashing has become one of the very basics of auth itself, and hence ignoring that would not be an intelligent decision.
Coming back to what it is, hashing is just one-way encryption of some text. What does that mean? It means it converts a set of characters we give to it into a very long and random string, which has 2 main characteristics you need to know:
- The same string is always converted to the same random string.
- The original string cannot be re-traced from the hashed string.
It is common practice to store passwords in a hashed form, and absolutely no good company would store passwords as just plain text. If you wish to learn more about hashing, you can check here.
That being said, now we can safely move to the next step, i.e. login.
Log A User In
To add the login route, change your file to:
If you see here, only lines 33–46 have been added. Initially, we are taking the hardcoded values of username and password, which are same as what we took in the register route. We are then finding a user with the said username from our database, and store it inside
user. Next, we compare the password of the given user with the password we have received. Remember that the password we have stored is a hashed version, and hence we cannot directly compare the values of the 2 strings. We will use
bcrypt.compare(), which is the inbuilt function for comparison in the library.
Note: Do not attempt to manually hash the password you have received and then compare strings, that does not work. To understand why, please read the concept of salt from the link above.
Next, we receive true or false inside the
matchstatus variable, and that tells us if the password was correct or not. We do our actions according to it.
Note: Currently, we are just doing console.log or res.send. This is not actually logging us in. This is just the code to check if the details are right or wrong. The actual logging in part actually occurs below, when we discuss sessions. We will update this route when that happens.
Now that we have completed the login route for now, let us move to sessions. Note we are covering sessions before logout as logout will not make any sense without it.
Add Sessions To The Site
First of all, there may be many people not aware about what sessions are, and what we need them for. Sessions are basically what the word suggests. You may have seen phrases like ‘session expired’ many times when you leave a site logged in for long without using it. That is the session we are taking about.
As a basic understanding, you can understand it as the memory the browser is keeping of our browsing. In general, HTTP requests do not have memory. Hence, even if you log in correctly, there is no way to let the rest of the site know that you are successfully logged in. This is where sessions would come in in auth. As a session memory is stored by the browser, it doesn't matter which URL we are going to inside the site, the data inside the session will always be there.
So our approach for logging someone in could be this: We create a variable named
user inside the session(of course you can have any variable name). When a user enters the correct details for authentication, we can set the
user variable inside the session to be the user details. If not logged in, it remains null. So at any point of time, if we need to check if the user is logged in, we just check if this variable is null or has a valid value.
Let’s see the code for this now:
Though the explanation made it look like we would be doing something very very complex, it is actually pretty simple to code. Let’s see the additions of the above code :
- Lines 4 –10: We require and configure express-session, which is the library we are using to create sessions and maintain them. These lines will configure the session in the browser, and make it extremely easy for us to access it, as we will see now.
- Line 47:
req.session.user = user;: As I mentioned above we set a variable called
userinside the session(which we access by
req.session)to the user we just logged in.
This successfully logs us in and we can now later access the details of the logged-in user, no matter what page we are on.
Let’s now see the final and easiest route of all, the logout route.
Log A User Out
As I mentioned above, to log a user out, we can just set the req.session.user to null. Here’s the code for the same:
Here, we have just added the logout route in lines 55–58. We set
req.session.user to null, and then return a response.
Okay, so this wraps up all the code for basic authentication. Now, you can (hopefully) implement register, login and logout functionality from scratch with ease. Like I mentioned earlier, there could have been a lot of improvements in this design, mostly in security. Note that this system is not at all safe from attacks, which is of course not desirable. However, that is a separate topic altogether and for some other day. For now, this is all I had to offer. Thanks for taking out time to read this article, and I hope it was helpful. Cheers!