User Authentication and Authorization using npm bcryptjs, JWT, and MongoDB Atlas

User Authentication and Authorization using npm bcryptjs, JWT, and MongoDB Atlas

Let us build an express API for this.

We will be going through the following steps to build this API.

1. Installation:

Make sure you have NodeJS, npm, and MongoDB installed on your system to move forward with the blog.

  • Create a project folder and use the terminal to run the command npm init -y
  • Let us first download the npm packages npm i express bcryptjs jsonwebtoken config mongoose

2. Initialize express app:

const express = require('express');   
const app = express();
app.get('/',(req,res)=> res.json({msg:"Hello World!"}));
const PORT = 5000;
app.listen(PORT,()=>console.log('Server started on port: 5000'));

Add the script "start": "node server.js" in package.json file and run npm start

Bravo! Our app is ready to run at localhost:5000

3. Connection to MongoDB Atlas and writing mongoose schema

  • Create an account at MongoDB Atlas
  • Create a cluster and add your connection string into your application code in a default.json file of the config folder. npm config organizes hierarchical configurations for app deployments.
    {
      "mongoURI": "mongodb+srv://<User>:<password>@cluster0.qkgl9.mongodb.net/<Database Name>"
    }
    
  • Create db.js file in same config folder to establish connection with database.
    const mongoose = require('mongoose');
    const config = require('config');
    const db = config.get('mongoURI');
    const connectDB = ()=>{
      //Connection to MongoDB cluster
      mongoose.connect(db,{
          useNewUrlParser: true,
          useCreateIndex: true,
          useUnifiedTopology:true,
          useFindAndModify: false         
      }).then(()=>console.log('MongoDB connected'))
      .catch(err=>{
          console.error("Database error",err.message);
          process.exit(1);               
      });
    };
    module.exports = connectDB;
    
  • Let us now create a mongoose schema for the user model (A Mongoose schema is a document data structure that allows you to define the fields stored in each document along with their validation requirements and default values. Mongoose model provides an interface to the database for CRUD operations.)
  • Create Users.js file in models folder to add the below schema.
    const mongoose = require('mongoose');
    const UserSchema = mongoose.Schema({
      email:{
          type:String,
          required:true,
          unique:true
      },
      password:{
          type:String,
          required:true
      },
      date:{
          type:Date,
          default: Date.now
      }
    });
    module.exports = mongoose.model('user',UserSchema);
    
  • Call connectDB() in server.js
    const connectDB = require('./config/db')
    //Connect database
    connectDB();
    

Before creating routes let us dive into npm bcrypt and jsonwebtoken.

bcryptjs

It allows us to save the password in the database with hashing technique in a secure way. Plain-text (unencrypted) passwords must not be stored in the database. It has to be encrypted and for this bcryptjs provides certain methods. The methods which we are going to use are

  • bcrypt.gensalt(saltRounds_value): Instead of hashing only the password bcrypt uses salt with the password. This method generates the salt which is a random string.
  • bcrypt.hash(userPassword, salt): By hashing a plain text password plus a salt, the hash algorithm’s output becomes unpredictable. Store hashed password in DB.
  • bcrypt.compare(password,user.password): when a user attempts to log in, we have to compare the password entered by the user with the one stored in the database.

jsonwebtoken

The JSON web token (JWT) is one method for allowing authentication, without actually storing any information about the user on the system itself. Authorization is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token. Read more about JWT at jwt.io.

4. User authorization using JWT

  • When the user registers or logs in to the account token gets generated for the user with the help of JWT. We can return the token as a JSON object with the API result.
  • We can store the token returned by the backend into the local-storage for the specific period and whenever the user tries to access the website we can send the token in the request header to get verified by the backend. After getting verified we can return user details to the frontend.
  • To verify the token let us create the auth.js middleware(a function that has access to request and response cycle object).
    const jwt = require('jsonwebtoken');
    const config = require('config');
    module.exports = function(req,res,next){      
      //Get token from request header
      const token = req.header('x-auth-token');
      if(!token){
          return res.status(401).json({msg:'No token, auth failed'});
      }
      try {
          //verify token & extract payload
          const decoded = jwt.verify(token,config.get('jwtSecret'));  
          req.user = decoded.user;
          next();
      } catch (err) {
          res.status(401).json({msg: 'Token is invalid'});
      }
    }
    
    Here I have saved the jwtSecret string in config folder's default.json file. We can use different strings for development and production by saving it in two different files.

5. Creation of Routes

  • We will be using Application-level and Router-level middlewares to handle the request coming from client.(Visit Using middleware to learn more about types of express middleware)
  • Add below routes in server.js
app.use(express.json({extended:false})); //bodyparser-Used to parse JSON bodies
//Define Routes
app.use('/api/users',require('./routes/users'));
app.use('/api/auth',require('./routes/auth'));
  • Create routes folder with auth.js and users.js files. auth route will help to authenticate the user at the time of login and authorize the user with the help of token verification middleware. users route will help to register the user and save details into the database. I haven't provided validation middleware, but you can add validations afterward.

auth.js:

const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const config = require('config');
const auth = require('../middleware/auth')
const User = require('../models/User');
//@route    GET api/auth
//@desc     Get logged in user
//@access   Private
router.get('/', auth, async (req,res)=>{
    try {
        //Get user details except the password
        const user = await User.findById(req.user.id).select('-password');  
        res.json(user); 
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server Error');
    }
});
//@route    POST api/auth
//@desc     Auth user and get token(login)
//@access   Public
router.post('/',
    async (req,res)=>{
    const {email,password} = req.body;
    try {
        let user = await User.findOne({email});
        if(!user){
            return res.status(500).json({msg:"Invalid credentials"});
        }
        const isMatch = await bcrypt.compare(password,user.password);
        if(!isMatch){
            return res.status(500).json({msg:"Password invalid"});
        }
        const payload = {
            user:{
                id:user.id
            }
        }
        jwt.sign(payload,config.get('jwtSecret'),
        {
            expiresIn:360000
        },
        (err,token)=>{
            if(err) throw err;
            res.json({token});
        })
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server Error');
    }
});
module.exports = router;

users.js

const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const config = require('config');
const router = express.Router();
const User = require('../models/User');
//@route    POST api/users
//@desc     User Registration
//@access   Public
router.post('/', async (req,res)=>{
    const { email,password } = req.body;
    try {
        let user = await User.findOne({email});
        if(user){
            return res.status(400).json({msg:'User already exist'});
        }
        user = new User({
            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,config.get('jwtSecret'),
            {
                expiresIn:360000
            },
            (err,token)=>{
                if(err) throw err;
                res.json({token});
            }
        )
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server Error');
    }
});
module.exports = router;

Now our express server is ready to authorize the user. Run npm start and test the API routes with the help of Postman. For better understanding, refer to my GitHub Repo.