Express Series 6 : Validation & Sanitization

Introduction

We Deep dive into add more security layer inside form , Validation Basically is verifying process after the client had entered all the necessary data and then pressed the Submit button , validation means checks if the input meets a set of criteria or not. Validating can set on the server side or client side.

What about sanitizing ? Sanitizing is process that will modifies the input to ensure that it is valid such as doubling single quotes , clear white spacing etc. You would normally combine these two techniques to provide in-depth defense to your application. In this series we wanna using Server Side Validation & Sanitization because **it’s more secure **using third party express middleware called Express Validator.

Validation

Say you have a POST endpoint that accepts the name, email and age parameters:

const express= require ("express");const bodyParser = require("body-parser");const app = express();app.post("/form",(req,res)=>{   const name = req.body.name;   const email = req.body.email;   const age = req.body.age;})

How do you server side validate those result to make :

  • name is a string of at least 3 character ?
  • email is a real email ?
  • age is a number , between 0 - 110 ?

That’s why we need Express Validator to handle this case . For using express validator simply just

npm install express-validator --save

call the package , you can call all parametes too in one require (body/check + validationResult).

In the controller:

const { validationResult } = require("express-validator");

In th the routes:

const { body } = require("express-validator");

You can see how to implement express vaidator in the project at the project section below. But the most important things is validator methods , This is most common validator methods that i’m using :

NoMethodsDescription
1isAlphanumeric()check if the string contains only letters and numbers.
2isCreditCard()check if the string is a credit card.
3isDecimal()check if the string represents a decimal number, such as 0.1, 4.0, etc.
4isEmail()check if the string is an email.
5isFloat()check if the string is a float.
6isLenght()check if the string’s length falls in a range.
7isUppercase()Check if the string is uppercase.
8isLowercase()check if the string is lowercase.
9isEmpty()check if the string has a length of zero.
10isURL()check if the string is an URL.
11isMobilePhone()check if the string is mobile number.
12isURL()check if the string is an URL.
Sanitization

There’s one thing you quickly learn when you deploy app into server : never trust the input  you need to sanitize make sure that people can’t enter weird things using client-side code and this is most common sanitize methods that express validator have :

NoMethodsDescription
1normalizeEmail()canonicalizes an email address.
2isCreditCard()check if the string is a credit card.
3toDate()convert the input string to a date, or null if the input is not a date.
4toFloat()convert the input string to a float, or NaN if the input is not a float.
5trim()trim characters (whitespace by default) from both sides of the input.
6blacklist()remove characters that appear in the blacklist. Ex : blacklist(input, ‘\\[\\]’).
Project

Let’s add Validation & Sanitization to our previous project (Fruit Shop)

first install express validator with npm install dont forget to require after that

Next , in Auth Controller

const bcrypt = require("bcrypt");
const User = require("../models/user");
const { validationResult } = require("express-validator");

exports.getLogin = (req, res, next) => {
  let message = req.flash("error");
  if (message.length > 0) {
    message = message[0];
  } else {
    message = null;
  }
  res.render("login", {
    pageTitle: "login",
    path: "/login",
    isAuthenticated: false,
    errorMessage: message,
    oldInput: {
      email: "",
      password: ""
    },
    validationErrors: []
  });
};

exports.postLogin = (req, res, next) => {
  const email = req.body.email;
  const password = req.body.password;

  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(422).render("login", {
      path: "/login",
      pageTitle: "Login",
      isAuthenticated: false,
      errorMessage: errors.array()[0].msg,
      oldInput: {
        email: email,
        password: password
      },
      validationErrors: errors.array()
    });
  }

  User.findOne({ email: email })
    .then(user => {
      if (!user) {
        req.flash("error", "Invalid email or password.");
        return res.redirect("/login");
      }
      bcrypt
        .compare(password, user.password)
        .then(doMatch => {
          if (doMatch) {
            req.session.isLoggedIn = true;
            req.session.user = user;
            return req.session.save(err => {
              console.log(err);
              res.redirect("/");
            });
          }
          req.flash("error", "Invalid email or password.");
          res.redirect("/login");
        })
        .catch(err => {
          console.log(err);
          res.redirect("/login");
        });
    })
    .catch(err => console.log(err));
};

exports.getSignup = (req, res, next) => {
  let message = req.flash("error");
  if (message.length > 0) {
    message = message[0];
  } else {
    message = null;
  }

  res.render("signup", {
    pageTitle: "signup",
    path: "/signup",
    isAuthenticated: false,
    errorMessage: message,
    oldInput: {
      email: "",
      password: "",
      confirmPassword: ""
    },
    validationErrors: []
  });
};

exports.postSignup = (req, res, next) => {
  const email = req.body.email;
  const password = req.body.password;

  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    console.log(errors.array());
    return res.status(422).render("signup", {
      pageTitle: "signup",
      path: "/signup",
      isAuthenticated: false,
      errorMessage: errors.array()[0].msg,
      oldInput: {
        email: email,
        password: password,
        confirmPassword: req.body.confirmPassword
      },
      validationErrors: errors.array()
    });
  }

  bcrypt
    .hash(password, 12)
    .then(hashedPassword => {
      const user = new User({
        email: email,
        password: hashedPassword,
        cart: { items: [] }
      });
      return user.save();
    })
    .then(result => {
      res.redirect("/login");
    })
    .catch(err => {
      res.status(500);
      console.log(err);
    });
};

exports.postLogout = (req, res, next) => {
  req.session.destroy(err => {
    console.log(err);
    res.redirect("/");
  });
};

Next , In Auth Routes

const express = require("express");
const router = express.Router();
const authController = require("../controller/auth");
const isAuth = require("../middleware/is-auth");
const User = require("../models/user");
const { body } = require("express-validator");

router.get("/signup", authController.getSignup);

router.post(
  "/signup",
  [
    body("email")
      .isEmail()
      .withMessage("Please Enter Valid Email")
      .custom((value, { req }) => {
        return User.findOne({ email: value }).then(userDoc => {
          if (userDoc) {
            return Promise.reject("Email Already Exist");
          }
        });
      })
      .normalizeEmail(),
    body("password", "Please Enter Valid Password & Minimal 5 Character")
      .isLength({ min: 5 })
      .isAlphanumeric()
      .trim(),
    body("confirmPassword")
      .trim()
      .custom((value, { req }) => {
        if (value !== req.body.password) {
          throw new Error("Password Not Match !");
        }
        return true;
      })
  ],
  authController.postSignup
);

router.get("/login", authController.getLogin);
router.post(
  "/login",
  [
    body("email")
      .isEmail()
      .withMessage("Please enter valid email.")
      .normalizeEmail(),
    body("password", "Please enter password at least 5 characters.")
      .isLength({ min: 5 })
      .isAlphanumeric()
      .trim()
  ],
  authController.postLogin
);

router.post("/logout", isAuth, authController.postLogout);

module.exports = router;

Little bit change on Login View

<%-include("includes/head.ejs")%>
<div class="container" class="mt-4 ml-2 mr-2">
  <br />
  <h4 class="text-center mt-4 mb-4">LOGIN</h4>
  <hr />
  <div class="row">
    <div class="col-sm-5 mx-auto">
      <% if (errorMessage) { %>
           <div class="alert alert-warning mb-2 mt-2 text-center font-weight-bold" role="alert">
              <%= errorMessage %>
           </div>
      <% } %>
      <form action="/login" method="POST" novalidate>
        <div class="form-group mt-4">
          <input
            type="email"
            class="<%= validationErrors.find(e =>e.param === 'email') ? 'form-control form-control-lg border border-danger' : 'form-control form-control-lg' %>"
            placeholder="Email"
            name="email"
            id="email"
            value="<%=oldInput.email%>"
          />
        </div>
        <div class="form-group">
          <input
            type="password"
            class="<%= validationErrors.find(e =>e.param === 'password') ? 'form-control form-control-lg border border-danger' : 'form-control form-control-lg' %>"
            placeholder="Password"
            name="password"
            id="password"
            value="<%=oldInput.password%>"
          />

        </div>
        <br />
        <div class="form-group text-center mx-auto">
       <button
            class="btn btn-lg btn-outline-primary mt-2 mr-2"
          >
             <a href="/signup" style="text-decoration:none;color: currentColor;"> SIGNUP</a>
          </button>
          <button
            type="submit"
            class="btn btn-lg btn-outline-success mt-2 ml-2"
          >
            LOGIN
          </button>
        </div>
      </form>
    </div>
  </div>
</div>

<%-include("includes/end.ejs")%>

Last on the Signup View

<%-include("includes/head.ejs")%>
<div class="container" class="mt-4 ml-2 mr-2">
  <br />
  <h4 class="text-center mt-4 mb-4">REGISTER</h4>
  <hr />
  <div class="row">
    <div class="col-sm-5 mx-auto">
     <% if (errorMessage) { %>
           <div class="alert alert-danger text-center font-weight-bold" role="alert">
              <%= errorMessage %>
           </div>
      <% } %>

      <form action="/signup" method="POST" novalidate>
        <div class="form-group mt-4">
          <input
            type="email"
            class="<%= validationErrors.find(e =>e.param === 'email') ? 'form-control form-control-lg border border-danger' : 'form-control form-control-lg' %>"
            placeholder="Email"
            name="email"
            id="email"
            value="<%=oldInput.email%>"

          />
        </div>
        <div class="form-group">
          <input
            type="password"
            class="<%= validationErrors.find(e =>e.param === 'password') ? 'form-control form-control-lg border border-danger' : 'form-control form-control-lg' %>"
            placeholder="Password"
            name="password"
            id="password"
            value="<%=oldInput.password%>"

          />
        </div>
         <div class="form-group">
          <input
            type="password"
            class="<%= validationErrors.find(e =>e.param === 'confirmPassword') ? 'form-control form-control-lg border border-danger' : 'form-control form-control-lg' %>"
            placeholder="Confirm Password"
            name="confirmPassword"
            id="confirmPassword"
            value="<%=oldInput.confirmPassword%>"
          />
        </div>
        <br />
        <div class="form-group text-center mx-auto">
          <button type="submit" class="btn btn-lg btn-outline-primary mt-2">
            REGISTER
          </button>
        </div>
      </form>
    </div>
  </div>
</div>

<%-include("includes/end.ejs")%>
Last Word

That’s  all about basic validation and sanitization using express validator . I really like this package because it help much for rapid development and security . Don’t forget to explore all the method that express validator have , as always link down below