loading...
Cover image for Authentication in NodeJS With Express and Mongo - CodeLab #1

Authentication in NodeJS With Express and Mongo - CodeLab #1

dipakkr profile image Deepak Kumar Updated on ・8 min read

I am starting a CodeLab Series in which I will building something cool and sharing with the community.

Today, We are going to implement Authentication API in Node using JWT, express, and MongoDB.

I advise you to follow the table of content and don't miss any steps. I will provide the full app code link at the end.

Table of Content


1. Introduction

Authentication - It is a process of identifying user identity.

User Authentication contains various steps, please check out this flowchart to know more. We will be using this flow to build the authentication system in our application.

Alt Text


2. Prerequisites

You should have prior knowledge of javascript basics, nodejs. Knowledge of ES6 syntax is a plus. And, at last nodejs should be installed on your system.


3. Packages Required

You will be needing these following 'npm' packages.

  1. express
    Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications

  2. express-validator
    To Validate the body data on the server in the express framework, we will be using this library. It's a server-side data validation library. So, even if a malicious user bypasses the client-side verification, the server-side data validation will catch it and throw an error.

  3. body-parser
    It is nodejs middleware for parsing the body data.

  4. bcryptjs
    This library will be used to hash the password and then store it to database.This way even app administrators can't access the account of a user.

  5. jsonwebtoken
    jsonwebtoken will be used to encrypt our data payload on registration and return a token. We can use that token to authenticate ourselves to secured pages like the dashboard. There would also an option to set the validity of those token, so you can specify how much time that token will last.

  6. mongoose
    Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. Mongoose supports both promises and callbacks.


4. Initiate Project

We will start by creating a node project. So, Create a new folder with the name 'node-auth' and follow the steps below. All the project files should be inside the 'node-auth' folder.

npm init

npm init will ask you some basic information about project. Now, you have created the node project, it's time to install the required packages. So, go ahead and install the packages by running the below command.

npm install express express-validator body-parser bcryptjs jsonwebtoken mongoose --save

Now, create a file index.js and add this code.

// File : index.js

const express = require("express");
const bodyParser = require("body-parser");

const app = express();

// PORT
const PORT = process.env.PORT || 4000;

app.get("/", (req, res) => {
  res.json({ message: "API Working" });
});


app.listen(PORT, (req, res) => {
  console.log(`Server Started at PORT ${PORT}`);
});

If you type node index.js in the terminal, the server will start at PORT 4000.

You have successfully set up your NodeJS app application. It's time to set up the database to add more functionality.


5. Setup MongoDB Database

We will be using MongoDB Database to store our users. You can use either a cloud MongoDB server or a local MongoDB server.

In this CodeLab, we will be using a Cloud MongoDB server known as mLab.

So, First, go ahead and signup on mLab. And follow the below steps.

  1. After successful signup, Click on Create New Button on home page.

  2. Now, choose any cloud provider for example AWS. In the Plan Type choose the free SandBox and then Click on the Continue button at the bottom right.

  3. Select the region(any) and click continue.

  4. Enter a DB name(any). I am using node-auth. Click continue and then submit the order on the next page. Don't worry it's free of cost.

  5. Now, You will be re-directed to the homepage. Select your DB i.e node-auth.

  6. Copy the standard MongoDB URI.

  7. Now, you need to add a user to your database. From the 5 tabs below, click on Users and add a user by clicking on Add Database User.

Now, you have got your database user. Replace the && with your DB username and password.

mongodb://<dbuser>:<dbpassword>@ds257698.mlab.com:57698/node-auth

So, the Mongo Server Address(MongoURI) should look like this. Don't try to connect on my MongoURI. It's just a dummy username & password. 😄😄

mongodb://test:hello1234@ds257698.mlab.com:57698/node-auth

Now, you have the mongoURI you are ready to connect your node-auth app to the database. Please follow the below steps.


6. Configure User Model

Let's go and first create a config folder. This folder will keep the database connection information.

Create a file named: db.js in config

//FILENAME : db.js

const mongoose = require("mongoose");

// Replace this with your MONGOURI.
const MONGOURI = "mongodb://testuser:testpassword@ds257698.mlab.com:57698/node-auth";

const InitiateMongoServer = async () => {
  try {
    await mongoose.connect(MONGOURI, {
      useNewUrlParser: true
    });
    console.log("Connected to DB !!");
  } catch (e) {
    console.log(e);
    throw e;
  }
};

module.exports = InitiateMongoServer;

Now, we are done the database connection. Let's create the User Model to save our registered users.

Go ahead and create a new folder named model. Inside the model folder, create a new file User.js.

We will be using mongoose to create UserSchema.

User.js


//FILENAME : User.js

const mongoose = require("mongoose");

const UserSchema = mongoose.Schema({
  username: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true
  },
  createdAt: {
    type: Date,
    default: Date.now()
  }
});

// export model user with UserSchema
module.exports = mongoose.model("user", UserSchema);

Now, we are done with Database Connection, User Schema. So, let's go ahead and update our index.js to connect our API to the database.

index.js

const express = require("express");
const bodyParser = require("body-parser");
const InitiateMongoServer = require("./config/db");

// Initiate Mongo Server
InitiateMongoServer();

const app = express();

// PORT
const PORT = process.env.PORT || 4000;

// Middleware
app.use(bodyParser.json());

app.get("/", (req, res) => {
  res.json({ message: "API Working" });
});


app.listen(PORT, (req, res) => {
  console.log(`Server Started at PORT ${PORT}`);
});


Congratulations 😄😄 , You have successfully connected your app to the MongoDB server.

Now, the next thing we have to do is make a /user/signup route to register a new user. We will see this in the next section.


7. User Signup

The Route for user registration will be '/user/signup'.

Create a folder named routes. In the 'routes' folder, create a file named user.js

routes/user.js


// Filename : user.js

const express = require("express");
const { check, validationResult} = require("express-validator/check");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const router = express.Router();

const User = require("../model/User");

/**
 * @method - POST
 * @param - /signup
 * @description - User SignUp
 */

router.post(
    "/signup",
    [
        check("username", "Please Enter a Valid Username")
        .not()
        .isEmpty(),
        check("email", "Please enter a valid email").isEmail(),
        check("password", "Please enter a valid password").isLength({
            min: 6
        })
    ],
    async (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({
                errors: errors.array()
            });
        }

        const {
            username,
            email,
            password
        } = req.body;
        try {
            let user = await User.findOne({
                email
            });
            if (user) {
                return res.status(400).json({
                    msg: "User Already Exists"
                });
            }

            user = new User({
                username,
                email,
                password
            });

            const salt = await bcrypt.genSalt(10);
            user.password = await bcrypt.hash(password, salt);

            await user.save();

            const payload = {
                user: {
                    id: user.id
                }
            };

            jwt.sign(
                payload,
                "randomString", {
                    expiresIn: 10000
                },
                (err, token) => {
                    if (err) throw err;
                    res.status(200).json({
                        token
                    });
                }
            );
        } catch (err) {
            console.log(err.message);
            res.status(500).send("Error in Saving");
        }
    }
);

module.exports = router;

Now, we have created the user registration in 'routes/user.js'. So, we need to import this in index.js to make it work.

So, the updated index file code should look like this.
index.js


const express = require("express");
const bodyParser = require("body-parser");
const user = require("./routes/user"); //new addition
const InitiateMongoServer = require("./config/db");

// Initiate Mongo Server
InitiateMongoServer();

const app = express();

// PORT
const PORT = process.env.PORT || 4000;

// Middleware
app.use(bodyParser.json());

app.get("/", (req, res) => {
  res.json({ message: "API Working" });
});


/**
 * Router Middleware
 * Router - /user/*
 * Method - *
 */
app.use("/user", user);

app.listen(PORT, (req, res) => {
  console.log(`Server Started at PORT ${PORT}`);
});

Let's start the user registration using postman. A postman is a tool for API testing.

Alt Text


8. User Login

Now, it's time to implement the Login router which will be mounted on '/user/login'.

Here is the code snippet for login functionality. Add the below code snippet in user.js


router.post(
  "/login",
  [
    check("email", "Please enter a valid email").isEmail(),
    check("password", "Please enter a valid password").isLength({
      min: 6
    })
  ],
  async (req, res) => {
    const errors = validationResult(req);

    if (!errors.isEmpty()) {
      return res.status(400).json({
        errors: errors.array()
      });
    }

    const { email, password } = req.body;
    try {
      let user = await User.findOne({
        email
      });
      if (!user)
        return res.status(400).json({
          message: "User Not Exist"
        });

      const isMatch = await bcrypt.compare(password, user.password);
      if (!isMatch)
        return res.status(400).json({
          message: "Incorrect Password !"
        });

      const payload = {
        user: {
          id: user.id
        }
      };

      jwt.sign(
        payload,
        "randomString",
        {
          expiresIn: 3600
        },
        (err, token) => {
          if (err) throw err;
          res.status(200).json({
            token
          });
        }
      );
    } catch (e) {
      console.error(e);
      res.status(500).json({
        message: "Server Error"
      });
    }
  }
);

Alt Text

9. Get LoggedIn User

Now, your User Signup and User Login is working, and you are getting a token in return.

So, our next task will be to Retrieve the LoggedIn user using the token. Let's go and add this functionality.

The /user/me route will return your user if you pass the token in the header. In the file route.js, add the below code snippet.

/**
 * @method - GET
 * @description - Get LoggedIn User
 * @param - /user/me
 */


router.get("/me", auth, async (req, res) => {
  try {
    // request.user is getting fetched from Middleware after token authentication
    const user = await User.findById(req.user.id);
    res.json(user);
  } catch (e) {
    res.send({ message: "Error in Fetching user" });
  }
});

As you can see, we added the auth middleware as a parameter in the /user/me GET route, so let's define auth function.

Go ahead and create a new folder named middleware. Inside this folder, create a file named auth.js

This auth middleware will be used to verify the token, retrieve user based on the token payload.

middleware/auth.js


const jwt = require("jsonwebtoken");

module.exports = function(req, res, next) {
  const token = req.header("token");
  if (!token) return res.status(401).json({ message: "Auth Error" });

  try {
    const decoded = jwt.verify(token, "randomString");
    req.user = decoded.user;
    next();
  } catch (e) {
    console.error(e);
    res.status(500).send({ message: "Invalid Token" });
  }
};

Yayy !! You have successfully created an authentication API in nodejs. Now, You can go ahead and test the /user/me endpoint after logging in.

How to Test the application?

PostMan is required for Testing the API. If you don't have PostMan installed first, install it.

  1. First, register the user or login if you are already registered.

  2. From step 1, you will get a token. Copy that token and put in the header.

  3. Hit Submit

Here is a preview of testing.

Alt Text

10. Conclusion

In this CodeLab - 1, we covered authentication in nodejs using express, jsonwebtoken and MongoDB. We learned about how to write middleware.

Here is the link of full code for this CodeLab: https://github.com/dipakkr/node-auth.

Also, I would love to know what else you want to cover in the next CodeLabs.


I am glad you read till here, Please give some ❤️ ❤️ !!

If you face in problem in running/understanding this application, let me know in the comments. Don't forget to give your feedback. Getting feedback helps me improve.

Subscribe to my email newsletter and stay updated!

I write about new stuff almost daily. Please follow me on Twitter | Instagram

Discussion

pic
Editor guide
Collapse
erknuepp profile image
erknuepp

I cannot get the test for the signup step to work in postman. I get errors for username, email and password. I have copied exactly what is in the github repo. It is also not adding an entry to the DB.

Collapse
ts22082 profile image
Thomas W. Smith

I was getting the same error. i fixed it by adding

app.use(express.urlencoded({ extended: true }));
app.use(express.json());

under

app.use(bodyParser.json());

index.js ends up looking like this:

const express = require("express");
const bodyParser = require("body-parser");
const user = require("./routes/user");
const InitiateMongoServer = require("./config/db");

// Initiate Mongo Server
InitiateMongoServer();

const app = express();

// PORT
const PORT = process.env.PORT || 4000;

// Middleware
app.use(bodyParser.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

app.get("/", (req, res) => {
  res.json({ message: "API Working" });
});

/**
 * Router Middleware
 * Router - /user/*
 * Method - *
 */
app.use("/user", user);

app.listen(PORT, (req, res) => {
  console.log(`Server Started at PORT ${PORT}`);
});

Collapse
megumievans profile image
megumiEvans

I have the same issue "cannot get" I think that everything is well configured in postman and I add this lines suggested but still it didn't work... and I'm testing with the original repo I can connect to my db but I can't get anything with submit

Collapse
lesupernom profile image
Alex

Old comment, but...

In Postman, go to the 'Headers' tab and set the 'Content-Type' value to 'application/json', by default it sends 'x-www-form-urlencoded'

Collapse
dipakkr profile image
Deepak Kumar Author

Did you add/replace your username password in MongoURI ?

Collapse
erknuepp profile image
erknuepp

Yes, it is not a DB connection issue. No errors on the console.

Thread Thread
dipakkr profile image
Deepak Kumar Author

Try running the GitHub clone with your Db username password.

Thread Thread
yugyndprodigy10 profile image
Eugene Taabazuing

How do you now connect it to a frontend login/signup page?

Thread Thread
dipakkr profile image
Deepak Kumar Author

Hi Eugene,

You can simply call the API using axios or fetch to get data.

Here is a sample code snippet on how you can do it.

const config = {
            headers: {
                'Content-Type': "application/json"
            }
 };

const formData = {
input_username,
input_passwrd
};

const response = await axios.post('/user/login', formData, config);
Enter fullscreen mode Exit fullscreen mode

I hope this answers your question.

Thread Thread
yugyndprodigy10 profile image
Eugene Taabazuing

It does, thanks!

Collapse
143umohsinkhan profile image
Mohsin Khan

Nice article, quick and properly demonstrated. just one note. below "secret" is used for token, instead of 'randomString'.

jwt.sign(
payload,
"secret",
{
expiresIn: 3600
},
(err, token) => {
if (err) throw err;
res.status(200).json({
token
});
}
);

Collapse
dipakkr profile image
Deepak Kumar Author

Thanks for correcting. I have updated in the code !

Collapse
alvarezskinner profile image
Andres Alvarez Skinner

Did you just do it in the repo? The blog entry is still showing the previous i believe. The thing is in signup you are using "randomString" whereas in login you are using "secret". The middleware is using "secret" so maybe making all those consistent, or even better moving it to a environment variable, would be better.

Apart from that, lovely blog entry! Thanks!

Thread Thread
dipakkr profile image
Deepak Kumar Author

Thanks @andres for feedback. I have just updated it in the blog post also.

Collapse
cortesben profile image
UI Cortés

Really great article. On the last step in the user.js file I didn't see mentioned to import const auth = require("./../middleware/auth");

I really just wanted to say thank you for this straight forward step by step tutorial.

Collapse
animir profile image
Roman Voloboev

Thank you for the article!

It is better to have one error message User not exists or Incorrect password instead of two different because of security.

I suggest you to add login brute-force protection with rate-limiter-flexible package. You can read more here

Collapse
chilupa profile image
Pavan Chilukuri

Fantastic article. Covered all major topics - Sign up, Login, Authentication using JWT, MongoDB through this app.

Here are few changes that I had to do as mLab is now part of MongoDB family.

  • Created an account with MongoDB Atlas
  • Created a cluster
  • In the cluster creation process, it will let you choose the type of Sandbox, AWS region. Once you are done with it, it will ask to create user and password for the database.
  • After created user and password, use the Connect to Application option which helps you select the type of app. Here it is Node and it will only give you the URI. I simply copied that URI to the MongoURI in the code (config/db.js)
  • Also, in routes/user.js file, I had to add this line at the end of file.
module.exports = router;

Hope this will help someone like me 😉

Collapse
dipakkr profile image
Deepak Kumar Author

Yes, you are right! thanks for sharing !!

This is the process for new users.

Collapse
chanpark_73 profile image
Chan Park

Love the blog, I learned so much.

Will there be a part 2 showing how to use the JWT token to navigate through private pages, such as the user's edit page? I know there's resources out there that show how to do it, but I like your style of code!

Collapse
dipakkr profile image
Deepak Kumar Author

Yes, definitely I am writing a series of articles on these.

Feel free to subscribe to get an instant update when I post the blog.

dipakkr.substack.com

Thanks a lot ! I am glad you liked the post.

Collapse
iprosites profile image
Arion

This article is absolutely fantastic and very timely for me. Everything worked perfectly. I did want to point out one thing in the "/me" function that was confusing but I figured it out. Quick notes in case the screenshot doesn't update:

  • @method - POST should be GET in the "/me" function (minor typo)

  • A screenshot should be provided to show the const auth = require()... being added to the top of the script so we know where that "auth" came from.

  • Instead of saying "Now, ...", which seems like it's about to come up (but doesn't), it would be better to say "As you can see, we added the auth middleware as a parameter in the get function...". I was confused since the const auth was not shown and on first view I missed the "auth" in the get "/me" function since the "Now, " made me think it was coming up. Stupid mistake on my part but I think the combination of these 2 things made for some confusion.

Collapse
dipakkr profile image
Deepak Kumar Author

Hi Arion,

I really appreciate your reply and the time you took to write such great feedback.
I am absolutely overwhelmed. I have done the required changes. .

Thanks a lot and wish you a very happy new year!

Collapse
cwills0114 profile image
Cwills0114

Would anyone be able to help me?
Looking to try and convert the email to all lowercase before it gets posted to the database within the signin and then convert to lowercase before it gets compared on login?

Thanks

Collapse
dipakkr profile image
Deepak Kumar Author

In JavaScript to convert a string to lowercase you can string method 'toLowerCase()'

I hope it answers your question.

Collapse
cwills0114 profile image
Cwills0114

Thanks for the response,

I have managed to figure this out.
Just as simple as adding a Constraint into the model for
Lowercase: true

Collapse
codecitrus profile image
Gautham

I noticed that we need to "await" on the existing user check before saving the new user. If I'm not mistaken, it would be possible for 2 identical request to "await" at this line at the same time, then both allowed to proceed to save duplicate users.

Any recommended way around this?

Anyways, thanks for article - learned a lot :)

Collapse
tarun_geek profile image
Tarun Nagpal

I am getting the following error.

Error: Illegal arguments: undefined, undefined
at _async (/home/tarun/Projects/extra/node-auth/node_modules/bcryptjs/dist/bcrypt.js:286:46)
at /home/tarun/Projects/extra/node-auth/node_modules/bcryptjs/dist/bcrypt.js:307:17
at new Promise (<anonymous>)
at Object.bcrypt.compare (/home/tarun/Projects/extra/node-auth/node_modules/bcryptjs/dist/bcrypt.js:306:20)
at router.post (/home/tarun/Projects/extra/node-auth/routes/user.js:117:36)
at process._tickCallback (internal/process/next_tick.js:68:7)

Can you please help me here ?

Collapse
divsyntax profile image
Div Syntax

Nice! I just started learning Node.js this wknd.

Collapse
dipakkr profile image
Deepak Kumar Author

I hope it's helps !

Collapse
divsyntax profile image
Div Syntax

Thanks, it does!

Collapse
krishnay2000 profile image
Krishnaraj Yadav

TypeError: Cannot read property 'id' of null.
I am getting the above error while signup. Infact, the data is saving in mongoDB but it is returning error status 500.Below is where there is problem with the code i think so.Tried many solution but none is working Please help to resolve this.
const payload = {
user: {
id: user.id
}
};

Collapse
ioannesi profile image
Ioannesi

Hello! I am trying to built an app with a platform login which is connect with mongodb cluster. so, I changed the MongoUri with connection link with my cluster but I have this message to the terminal screen.
dev-to-uploads.s3.amazonaws.com/i/...
dev-to-uploads.s3.amazonaws.com/i/...
is problem that there is not server.js file?
Do i have to add something in Json?
I read so many hours and there is noone to help.
Sorry for my "English writting".

Collapse
josecage profile image
José Cage

Thank you so much for your article. Really helped me.

I just faced a error with auth middleware:

TypeError: Cannot read property 'header' of undefined

The error comes from

const token = req.header('token');
Collapse
rizqiya profile image
Saputra, RW

Thanks for the amazing explanation, I tried this with similar code as you type. But I got a 404 response error when testing the API from google chrome. I tried looking for some solving problems in StackOverflow but didn't work. Do you have any idea what is the reason?

Collapse
giangvubinhng profile image
Giang Nguyen

Hey, I was able to sign up and log in but when I try to go to /me, I get an error like this: {"message":"Auth Error"}
please help!!

EDIT: this was a mistake on my part, i forgot to add token to the header! Great tutorial, thank you so much. I'd love to see you making a tutorial on how to connect it to a front end (Angular would be nice haha!)

Collapse
hsct profile image
HSCT

Can you also show me how to write logout API?

Collapse
shivamsrivastav profile image
Shivam Srivastava

Yeah its help me, I am tring to understand authencation for very long time, I read so many article but unable to understand. You make my day, its vey simple... keep posting article like this.. -:)

Collapse
mmimonir profile image
Collapse
dipakkr profile image
Deepak Kumar Author

Thanks a lot !

Collapse
sphrases profile image
sphrases

Thank you for posting this! I am working on an application that will use a very similar stack :D Good timing!

Collapse
gronkdaslayer profile image
gronkdaslayer

Why do this yourself? Just use AWS Cognito. You should never use that sort of DIY stuff really. Use this as a learning tool instead.

Collapse
badaranzale profile image
Ⓕⓝ ⓕ⑧

Hello excellent tuto bravo à vous mais concernant le test de connexion de recuperation de "me" postman ne sort rien comme resultat aussi j'ai pas d'erreur j'ai recommencer en creant un autre user/signup mais quand je lance c'et la meme chose rien ne sort i need help

Collapse
vevi1 profile image
vevi1

cant i use the same routes thats created already? because it seems not to creating on my vs.code.

Collapse
llalalalakakaka profile image
Lu

Would this be easy to implement into a previous project using MongoDB, Node, and Express?

Collapse
tarun_geek profile image
Tarun Nagpal

Good post

Collapse
florencemawusi profile image
FlorenceMawusi

Thanks a lot! I use Atlas instead of mLab.

Collapse
rjpeek profile image
rjpeek

Is there a part 2 to this article?

Collapse
rangoski profile image
Vivek Dwivedi

I loved everything but /me api is gving auth error to me . Can you please help. Also the token is changing every time ,What is reason behind it ?

Collapse
reillycooper profile image
Jacob Reilly-Cooper

Hey Vivek,

you need to require auth from the middleware folder in your user.js file:

const auth = require("../middleware/auth"); 
Collapse
kenwaysharma profile image
kenwaysharma

How do i connect this to front end? I have successfully connected the login and signup page but the user/me gives me "Auth Error". Looking for an answer. Thanks in advance.

Collapse
neilmorgan profile image
neil-morgan

Has there been any thought put in to how a log out function would look in this?

Collapse
anandks1993 profile image
Anandks1993

WOW!! Thank you for this great article

Collapse
dipakkr profile image
Deepak Kumar Author

Thanks ! Glad you like it !