Authentication with react, express, mongoose and jwt - part 2

In the previous blog I had covered what authentication is, why json web tokens are used and the front end part of authentication. Now let us see how we can setup a server which authenticates the user.

I'll be using MongoDB for storing the users records, i have skipped the MongoDB setup part. I'll be using repl to setup the server.

Connecting to the DB using node

First go to repl and create a new node repl. Now you can either go through the examples given and select express or write the following code in index.js

const express = require('express');

const app = express();

app.get('/', (req, res) => {
  res.send('Hello Express app!')
});

app.listen(3000, () => {
  console.log('server started');
});

With this basic express server is setup. Let us connect node with our MongoDB.

const express = require('express');
const app = express();
const mongoose = require('mongoose');

const uri = 'your_mongodb_uri';

mongoose.connect(uri, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
  .then(() => console.log("successfully connected"))
  .catch(error => console.error("mongoose connection failed..", error))


app.get('/', (req, res) => {
  res.send('Hello Express app!')
});

app.listen(3000, () => {
  console.log('server started');
});

MongoDB URI must be passed in mongoose.connect to connect to mongoDB Atlas database and if connection is successful then successfully connected is displayed in the terminal.

Setup a user model in MongoDB

Now let us make a user model in MongoDB which will help us store user records.

const mongoose = require("mongoose");

const {Schema} = mongoose;

const UserSchema = new Schema({
  name: {type:String, required: true},
  email: {type:String, required: true},
  password: {type:String, required: true},
}, {timestamps: true});

const User = mongoose.model("User", UserSchema);

module.exports = User;

We must first import mongoose and then schema from mongoose so that we can create a User schema. In the user schema the required fields are name, email and password. Since all of these fields must be filled, we provide required as true. Then we create a user model using user schema and export it so that it can be used in other files.

Creating authentication route for login and signup of user

Now let us create an authentication route where we can have login and signup endpoints.

const express = require('express');
const bcrypt = require('bcryptjs');
const router = express.Router();
const bcryptSalt = bcrypt.genSaltSync(12);

const User = require('./user.model');
const jwt = require("jsonwebtoken");

router.post('/signup',async function(req, res) {
  try{
    const { email, password, name } = req.body;
    const user = await User.findOne({ email: email });
    if (!user) {
      const hashedPassword = bcrypt.hashSync(password, bcryptSalt);

      const newUser = await User.create({ name: name, email: email, password: hashedPassword });

      const token = jwt.sign({uid: newUser._id}, secret, {expiresIn: '24h'})
      res.status(201).json({
        success: true,
        token: token
      });
    } else {
      res.status(409).json({
        success: false,
        error: {
          message: "User Already Exists"
        }
      });
    }
  }catch(e){
      res.status(400).json({
        success: false,
        error: {
          message: e.message
        }
      });
  }  
});

router.post('/login', async function(req, res){
  const {email, password} = req.body;
  try{
  const user = await User.findOne({ email: email });

  if(user){
    const validPassword = bcrypt.compareSync(password, user.password);
    const token = jwt.sign({uid: user._id}, secret, {expiresIn: '24h'})
    if(validPassword){
      res.status(200).json({
        success: true,
        token: token
      })
    }
    else{
      res.status(401).json({
        success: false,
        error: {
          message: "Invalid password"
        }
      });
    }    
  }else{
    res.status(401).json({
        success: false,
        error: {
          message: "User not registered"
        }
    })
  }}catch(e){
      res.status(400).json({
        success: false,
        error: {
          message: e.message
        }
      });
  }  
});

module.exports = router;

While defining the authentication route, we must keep in mind that the password received from user cannot be stored as is, we must encrypt it and store in our database so that the actual password is not known to anyone except the user. For this purpose we use bcrypt.

During signup, the user email, name and password are obtained through the request body and the password is then encrypted. Using the encrypted password and other user details the user record is created in MongoDB users collection. For creating user record we must use Model.create, here model is User and we pass the required fields and create the record. After saving the record, a token is create by using jwt, we will discuss the process later.

During login, the user email and password are obtained through the request body. The record of the user is checked in database using the email id of the user and if the user is present in the database, the password is encrypted and compared to the password in the database, if its a match then the token is generated and the user logs in. If the password is not matching then we return response as error with an error message. If the user doesn't exists the same is conveyed through an error message. This router is then exported and can be used in the index.js as authentication router.

Now let us see what happens when creating a jwt. First we perform jwt.sign which takes a payload, a secret or private key and expire time of the token and returns the Json web token as string which can then be used to uniquely identify the user. The generated jwt is then sent as response to the user which is then stored in localstorage so that it can be used whenever required. The token expires based on the time given when creating the token.

The index.js after making all the authentication router related changes will be,

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
var cors = require('cors')
const mongoose = require('mongoose');

app.use(cors());
app.use(express.json());

const uri = 'your_mongodb_uri';

mongoose.connect(uri, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
  .then(() => console.log("successfully connected"))
  .catch(error => console.error("mongoose connection failed..", error))

const auth_router = require("./auth.router")

app.get('/', (req, res) => {
  res.send("Welcome to Authentication Api")
});

app.use('/auth', auth_router)

app.listen(3000, () => {
  console.log('server started');
});

Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any other origins (domain, scheme, or port) than its own from which a browser should permit loading of resources. This is used so that we can access the repl from other domains.

I have used /auth as the endpoint to access authentication router, and then we can make post request to either /auth/login or /auth/signup with the required arguments in the body of the request to login/register the user and receive jwt in response. You can refer to the following repl for the exact code

I hope this blog was useful and you enjoyed reading it. Thank you.

If you enjoyed and learned from this article, I will like to connect with you. Connect with me on LinkedIn to be part of my journey.