How to Validate and Sanitize an ExpressJS Form

When a web application accepts user input, you never know what data to expect. The form data may be invalid or even hazardous. The erroneous data may be due to a user making an unintentional mistake or a malicious hacker implementing an attack. Either way, validation and sanitization protect your Node.js application against inappropriate input.

In this tutorial, we'll:

  • Install the express-validator library for use with an ExpressJS server
  • Validate user input from a login form
  • Sanitize user input from a login form
  • Return validation errors to the user to improve the UX

By the end of this tutorial, you will know how to use express-validator to perform back-end validation and data sanitation.

This tutorial is part 4 of 7 tutorials that walk through using Express.js for user authentication.

Goal

Use the express-validator library to validate and sanitize a login form.

Prerequisites

What is express-validator?

Express-validator is a library used to validate and sanitize ExpressJS forms. It is based on validator.js and allows you to:

  • Utilize existing validation functions
  • Create custom validation conditions
  • Apply form sanitization functions
  • Design unique error messages

Why use express-validator?

It is a flexible and straightforward way to manage necessary form validation and sanitization. Although you can write your own code, it would take a lot of time, and you would need a lot of practice to ensure you didn't miss anything. The express-validator library is also widely used and accepted by the community.

Validate and sanitize a login form

In the previous tutorial, we created a login form using ExpressJS. In this tutorial, we will use express-validator to validate and sanitize the user input on our login form.

Here is the login code that we will add validation to, in the file /static/login.html:

<!DOCTYPE html>
<html>
  <head>
	<title>Example Login Form</title>
  </head>
  <body>
	<form action="//members.osiolabs.com/login" method="post">
  	<!-- user input-->
  	Username:<br>
  	<input type="text" name="username" placeholder="Username" required><br><br>
  	Password:<br>
  	<input type="password" name="password" placeholder="Password" required><br><br>
  	<!-- submit button -->
  	<input type="submit" value="login">
	</form>
  </body>
</html>

And the corresponding server.js:

const express = require('express'). // Include ExpressJS
const app = express(). // Create an ExpressJS app
const bodyParser = require('body-parser'); // Middleware

app.use(bodyParser.urlencoded({ extended: false }));

// Route to Homepage
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/static/index.html');
});

// Route to Login Page
app.get('/login', (req, res) => {
  res.sendFile(__dirname + '/static/login.html');
});

app.post('/login', (req, res) => {
  // Insert Login Code Here
  let username = req.body.username;
  let password = req.body.password;
  res.send(`Username: ${username} Password: ${password}`);
});

const port = 3000 // Port we will listen on

// Function to listen on the port
app.listen(port, () => console.log(`This app is listening on port ${port}`));

I assume you either followed the previous tutorial or have created an ExpressJS form on your own. Thus, you already have NodeJS and ExpressJS installed. If you do not have a functional ExpressJS form, please review the previous tutorial.

Install express-validator

Install the package with the following npm command:

npm install express-validator 

Include express-validator functions

Add the following to the top of your server.js:

// Include Express Validator Functions
const { check, validationResult } = require('express-validator');
  1. check is the primary function used to validate and sanitize input. It takes in 2 optional parameters:
  • field: The field parameter can be a string or an array of strings representing the input fields you want to check. If there is no field value, the entire request object will get checked.
  • message: The message parameter holds a string that contains a custom error message. If there is no message value, the default message is "Invalid value".
  1. validationResult is a function of the body request object (req) and is used to return an object containing all validation errors.

Identify rules for input fields

Before you write the validation code, you must identify what criteria the input must meet. For our example login page, let's say:

  1. username:
  • A valid email address
  1. password:
  • At least 8 characters
  • Contains a number
  • Contains a capital letter

Choose validation methods

Now that we know which rules to check for, we must identify the appropriate validator methods to use. You may do so by using the express-validator documentation or the complete list in the validator.js docs.

To check that the username is a valid email address we will use isEmail(). If the username input is not an email address, we will use the error message 'Username Must Be an Email Address'.

check('username', 'Username Must Be an Email Address').isEmail()

To check that the password is at least 8 characters, we use isLength(). If the password input is less than 8 characters, the error message is 'Password Must Be at Least 8 characters'. Instead of taking the message in as a parameter to the check function, we will use withMessage(). Doing so allows us to use a different error message for each possible password validation error.

check('password').isLength({ min: 8 }).withMessage('Password Must Be at Least 8 Characters')

To check that the password contains a number and to check that it includes an uppercase letter, we use .matches().

.matches('[0-9]').withMessage('Password Must Contain a Number')
.matches('[A-Z]').withMessage('Password Must Contain an Uppercase Letter')

Add validation to app.post

Insert an array of check functions as a parameter of the app.post function. In the example below, we store our validation array in a variable called loginValidate.

// Validation rules.
var loginValidate = [
  check('username', 'Username Must Be an Email Address').isEmail(),
  check('password').isLength({ min: 8 })
  .withMessage('Password Must Be at Least 8 Characters')
  .matches('[0-9]').withMessage('Password Must Contain a Number')
  .matches('[A-Z]').withMessage('Password Must Contain an Uppercase Letter')];

// Process user input.
app.post('/login', loginValidate, (req, res) => {
  // Insert Login Code Here
});

Add sanitation

In this project, we use 3 sanitization methods:

  1. trim() trims characters from input. By default (with no parameters) this method trims whitespace.
  2. escape() will replace certain characters (i.e. <, >, /, &, ', ") with the corresponding HTML entity.
  3. normalizeEmail() ensures the email address is in a safe and standard format.

To see a full list of sanitization options visit the sanitization section of validator.js’s documentation.

We add all 3 sanitizers to our check of the username.

check('username', 'Username Must Be an Email Address').isEmail().trim().escape().normalizeEmail()

We add trim() and escape() to our check of the password.

check('password').isLength({ min: 8 })
.withMessage('Password Must Be at Least 8 Characters')
.matches('[0-9]').withMessage('Password Must Contain a Number')
.matches('[A-Z]').withMessage('Password Must Contain an Uppercase Letter')
.trim().escape()

Our validation array should now look like this:

var loginValidate = [
  // Check Username
  check('username', 'Username Must Be an Email Address').isEmail().trim().escape().normalizeEmail(),
  // Check Password
  check('password').isLength({ min: 8 }).withMessage('Password Must Be at Least 8 Characters').matches('[0-9]').withMessage('Password Must Contain a Number').matches('[A-Z]').withMessage('Password Must Contain an Uppercase Letter').trim().escape()];

Check for errors

Inside our app.post’s callback function. we declare a variable called errors and set it equal to validationResult(req).

const errors = validationResult(req);

Next, we create an if statement. If there are validation errors, we want to display them to the user. If there are no errors, the application continues as normal. If the user input is all valid, our example application displays the inputted username and password.

To return the error result object we can choose from 2 methods:

  1. array() is the method used in the example below. It returns an array of all validation errors.
  2. mapped() will return an array containing only the first validation error for each field.
// Process User Input
app.post('/login', loginValidate, (req, res) => {
	const errors = validationResult(req);
	if (!errors.isEmpty()) {
    return res.status(422).json({ errors: errors.array() });
	}
	else {
    // Insert Login Code Here
    let username = req.body.username;
    let password = req.body.password;
    res.send(`Username: ${username} Password: ${password}`);
	}
});

Let’s say the user broke every input rule. The list of errors would look like this:

Example JSON out of a validation error

If there are no errors, you should see something like this:

Username: candy@cane.com Password: Wkdi98rs!1

Final server.js code

Now that we have finished writing the validation and sanitization code, server.js should look like this:

const express = require('express'). // Include ExpressJS
const app = express(). // Create an ExpressJS app
const bodyParser = require('body-parser'); // Middleware

// Include Express Validator Functions
const { check, validationResult } = require('express-validator');

app.use(bodyParser.urlencoded({ extended: false }));

// Route to Homepage
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/static/index.html');
});

// Route to Login Page
app.get('/login', (req, res) => {
  res.sendFile(__dirname + '/static/login.html');
});
// Validation Array
var loginValidate = [
  // Check Username
  check('username', 'Username Must Be an Email Address').isEmail()
  .trim().escape().normalizeEmail(),
  // Check Password
  check('password').isLength({ min: 8 }).withMessage('Password Must Be at Least 8 Characters').matches('[0-9]').withMessage('Password Must Contain a Number').matches('[A-Z]').withMessage('Password Must Contain an Uppercase Letter').trim().escape()];

/. Process User Input
app.post('/login', loginValidate, (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
  	return res.status(422).json({ errors: errors.array() });
  }
  else {
. // Insert Login Code Here
. let username = req.body.username;
. let password = req.body.password;
. res.send(`Username: ${username} Password: ${password}`);
  }
});

const port = 3000// Port we will listen on

// Function to listen on the port
app.listen(port, () => console.log(`This app is listening on port ${port}`));

Recap

It is essential to validate and sanitize user input. The express-validator library makes it easy to implement standard and custom validation and sanitization. The check function implements conditions, and the validationResult function accesses validation errors.

An array of check functions get passed into app.post() and validationResult() is used inside app.post()'s callback.

Keep going with the next tutorial in this set: What Is the Difference Between Sessions and JSON Web Tokens (JWT) Authentication?.

Further your understanding

  • What are some of the limitations of the express-validator library?
  • How would you check if a password contains a special character?
  • Why would you validate input on a login form?

Additional resources

Sign in with your Osio Labs account
to gain instant access to our entire library.

Data Brokering with Node.js