Skip to content

Create a secure & structered web application

qiubee edited this page Jun 23, 2020 · 17 revisions

The following article describes the different actions taken to create a secure and well structured Node application with Express as framework and describes how to deploy a Node application. In this article the dating app Liev has been used as example. The platform and framework that have been used are Node.js and Express.

  1. Backend structure
    1. Route modules
    2. Middleware
    3. Error Handling
    4. MVC model
  2. Security
    1. HTTP Headers
    2. Ratelimiter
    3. Hashing
    4. Authentication / Authorization
  3. Deployment
    1. Deploy with Heroku
  4. Resources

Backend structure

A well built backend structure is vital for clean and readable code, and for understanding the app structure. The following topics describe what actions you can take to build a clear backend structure.

Route modules

What are modules?

A module is a piece of code with a particular function you can use elsewhere, in another code file. You can import code from another file to use it in your own code file and export code you've created yourself and use it in another code file. When you want to use code from a module you use the import statement. When you want to use the code you created in a file and want to use it in another file you export the code. To do that you use the export statement.

Using this method we can divide code into seperate files that each have their own function. This way you create a clear overview with each module built for a particular purpose.

Import & export example

The following code shows an example of creating a module, exporting the code and importing it in a new JavaScript file. This example uses the ECMAScript 6 version of importing and exporting code, mainly used client-side.

First we create a file that exports a name.

const name = "Brian";

export { name };

We save this file as name.js. This is now a module. Now we can import the module we just created inside a new JavaScript file.

import { name } from "./name.js";

console.log(name);
// -> Brian

When we log the name, it shows Brian.

Node.js modules

In Node.js the import and export statements are slightly different. Instead of using the ECMAScript 6 version of importing and exporting code it uses CommonJS syntax.

To import a module you use the require() statement. To export you use exports. If you have a single function you want to export as a module, you can use module.exports. If there are multiple functions you may want to export using exports.

Node.js import & export example

The following code shows an example of creating a module, exporting the code and importing it in a new JavaScript file. This example uses the CommonJS version of importing and exporting code, mainly used server-side.

First we create a file that exports a name.

const name = "Brian";

module.exports = name;

We save this file as name.js. This is now a module. Now we can import the module we just created inside a new JavaScript file.

const name = require("./name.js");

console.log(name);
// -> Brian

When we log the name, it shows Brian.

Using route modules in Node.js

Route modules are files with functions for a dedicated route. You can use those files in your Node.js application or use a dedicated router file as middleware (shown in the example below) to let the application know what to do when a user navigates to a page or sends information to the server. This way you have different files each handling a specific route. It also makes your code better readable, because it isn't written in a single file which can get quite large pretty fast.

In the example shown below I use route modules in the router file, which handles the actions taken when a user navigates to a different page on the website.

To use the Express router I imported the router module. I required Express. To get the Router object for handling routing I used .Router() which only imports the router.

To use the router I've made several GET requests to the homepage, login and signup pages. Inside the get requests I stated the route path it will look for and instead of using a function on each request I use modules: a login module to handle the GET request to /login, a signup module to handle the GET request to /signup and the index module to handle the GET request to the index page of the website. I imported all the modules before using it in the router and by doing it this way I have seperated the code for each route.

// Section of code used in the router.js file

// importing the Router object
const router = require("express").Router();

// The router modules
const index = require("./controllers/index"),
    login = require("./controllers/login"),
    //
    signup = require("./controllers/signup"),
    //

// using the modules in the requests router
router.get("/", index)
    .get("/login", login.get)
    //
    .get("/signup", signup.get)
    //

// exporting the router as a module
module.exports = router;

Middleware

What is middleware?

Middleware is a function that can interacts with the request and response objects received when a user sends a browser request to the server. The request object contains information that tells the server what page has been requested by the browser. The response object contains the information that the server has send back to the browser containing the actual page and / or information about the status of the response. When you create middleware you process what the server has to to with the request and what it has to send back to the browser.

Middleware used in the dating app

Inside the server.js code file below you can see that a module errorHandler has been used in the application. To use the middleware I use app.use() with inside the parenthesis the errorHandler function. Now on every request it will use the errorHandler function when an error has been occured.

// Section of code used in the server.js file

// importing express and the modules that will be used as middleware
const express = require("express");
const router = require("./routes/router");
const errorHandler = require("./routes/errorHandler");

// creating the express app
const app = express();

app.set("view engine", "hbs")
    //
    .use(router) // the middleware used to handle routing
    .use(errorHandler) // the middleware used to handle errors
    //

To see how error handling and the errorHandler function works, you can read the Error handling section below. For more information on how the router function works, you can read Using modules in Node.js.

Error handling

What does error handling mean?

Error handling is the process of dealin with errors. There are different error codes you can encounter when a request has been sent to the server. On each response there is a HTTP response code. The following list shows which HTTP codes are most common:

Status code Information Explanation
200 OK This indicates that everything so far is OK and that the client should continue the request.
304 Not Modified This indicates that the response has not been modified, so the client can continue to use the same cached version of the response.
404 Not Found This indicates that the server can not find the requested resource.
500 Internal Server Error This indicates that the server has encountered a situation it doesn't know how to handle.

In Express you mostly encounter the 404 and the 500 errors. A 404 error means that the page a user requested does not exist and the server can't give anything back that has not been defined. A 500 error means that something on the server has gone wrong and doesn't know what to do.

Handling the errors

In Express you there is a function that can pass errors. When you create a function with the err (error) and next parameters, you create a error handling function. The following code is an example of a error function:

app.use(function (err, req, res, next) {
  console.error(err)
  res.status(500).send('Something broke!')
})

In the dating app I created a error handling module that is handling each error code. Inside the errorHandling.js file I made the error handler.

function errorHandler (err, req, res, next) {
    if (!err.status) {
        err.status = 500;
    }
    console.log(err);
    switch (err.status) {
    case 404:
        return res.render("error", {
            title: "This page is empty"
        });
    case 500:
        return res.send("500 - Server error");
    }
}

module.exports = errorHandler;

When a page doesn't exist I send back a page with the message that the page does not exist. When there is an unkown error status it will set the status to 500 and will send back the text 500 - Server error to the client.

MVC model

MVC is a model that stands for Model (M), View (V), Controller (C). Its a design pattern that has the goal to seperate functionality into three components:

Each component handles a dedicated part of the entire application that either handles requests, interact with the data or has to render the correct page.

Model

The model is the part that controls the manipulation with the data. It is responsible for getting and changing the data. This means it adds data to the database, changes data stored in a database, or deletes data from the database. It primarily does this based on the actions taken on behalf of the controller. The controller can request data from the model to render the view.

View

The view is the actual page the user gets back from the server. The view consists of HTML and CSS, it is the page the browser can understand. A view can also be dynamic. This means that it can render different parts of the page based on the data passed from the controller. A controller can request data from the database and pass it to the view. The view can then insert the data in the page and render the final page. To pass dynamic values to the view you have to use a templating engine. A templating engine is a system with its own syntax and logic that can render dynamic values inside a page.

Controller

The controller is the part that handles what to do when it receives a request from the user and what to send back to the user. It processes the request, e.g. a GET request or a POST request and processes the input from the user, it interacts with the model to request, manipulate or add data, and it passes the dynamic values from the model to the view to render the page and send the page with the correct information to the user.

Using the MVC model in an application

Inside the dating app I used the MVC model to seperate the functions of handling requests, interacting with data and rendering the correct page. The app now has the following tree structure:

.
├── [database]
│   └── database.js
│
├── [modules]
│   ├── age.js
│   ├── country.js
│   └── week-number.js
│
├── [public]
│   ├── [css]
│   ├── [fonts]
│   ├── [images]
│   └── [js]
│
├── [routes]
│   ├── [controllers]
│   ├── errorHandler.js
│   └── router.js
│
├── [views]
│   ├── [layouts]
│   ├── [partials]
│   ├── about.hbs
│   ├── cat.hbs
│   ├── create-profile.hbs
│   ├── error.hbs
│   ├── home.hbs
│   ├── index.hbs
│   ├── login.hbs
│   ├── mail.hbs
│   ├── profile.hbs
│   └── signup.hbs

With this structure each functionality is placed inside its own folder. The model is inside the database folder and the database file manages the interaction with the database, the view is inside the view folder and manages what part of a page and which values have to be rendered based on the values passed by the controller, and the controller is inside the controllers folder inside the routes folder to handle each request and sends back the correct page to the user.

To be able to have the controller interact with the model and view I've made a database module to interact with the database and used a templating engine to pass values and render the page. The templating engine I used is Handlebars, because its syntax is reminicent to the JavaScript syntax and uses JavaScript logic to render different parts of a page based on the values passed from the controller.

Security

To make your application more secure against possible scripting attacks or malicious intent you have to make sure your security is set up. The following topics describe what you can do to add security in your code.

HTTP Headers

Ratelimiter

Hashing

What is hashing?

Hashing is a method where a computer converts and is mixing an input to a string with letters and numbers with a specific length.

Why use hashing?

To use hashing on content you can be sure the piece of content is uniquely identifiable, meaning if the content has been edited and hashed it will create a different hash string. Hashing is also used because it is irreversable, meaning you can't get the same hash with a different hash function used to hash the input. It has to be precisely the same hash function the input has been hashed with.

Using hashing to store sensitive information

When creating an application with authentication features you have to store sensitive information like a password. With the hashing method you can create a unique string hash and hide the real password in the hash string. By saving the hash in a database you leave the risk of storing plain text password which is a security risk.

How to hash a password in Express

To hash a string you can use a package that does the hashing for you. I used Bcrypt has the hashing package. Bcrypt helps you to change passwords into hash strings. In the example below you can see Bcrypt implemented inside the signup controller.

// Section of code used in the signup.js (controller) file

// import the bcrypt module
const { genSalt, hash } = require("bcrypt");

// this function creates a hash from a string and returns the hash
async function hashString (string) {
    try {
        const salt = await genSalt();
        const hashed = await hash(string, salt);
        return hashed;
    } catch (err) {
        console.error(err);
    }
}

async function post (req, res) {
    // the email and password are stored in the request body
    const user = req.body

    // 1. the password string is stored inside user.password
    // 2. then the password is passed to the hashString function
    // 3. the returned hash string is stored in the password variable
    const password = await hashString(user.password[0]);
}

With this code I created a function that hashes a string input. When a user sends a form with an email and password to the server, then the post function will store these credentials to the database. To hash the password I have to get the password from the body. I do that with user.password[0]. Then I pass the user password to the hashString function and bind the returned hash to the password variable. Inside the function you "salt" the password, meaning the amount of times the password will get mixed up. How higher the salt, the more times it has created a different. With the password hashed I can now store the hash in the database without the password being human readable. Because Bcrypt has the ability to retrieve the original content from the hash it's possible to check when a user logs in if the credentials are correct.

Authentication / Authorization

When you create an app you might want to have a user to create an account and login to that account to have the ability to use features of your app. To do that you need authentication. When you don't want a user to do anything on your website without certain permission you need authorization. These concepts are each described in the sections below.

What is authentication?

Authentication is the method to verify an identity. On the web it means whether a user is known by the website by looking up the users credentials inside the database.

Methods to authenticate

There are multiple methods to authenticate a user. There is either stateful authentication or stateless authentication.

Stateful authentication is the method when the information to verify a user is stored server-side as a session, meaning the information either is saved on the server in memory, in cache or saved in a database. The most common way for stateful authentication is to use sessions.

What is authorization?

Authorization is the method to verify for user permission. On the web it means that a user either has permission to perform a certain action on website or not.

Deployment

When your done building your application you possibly want it to be accessible for anyone to use your application. The following topic describes how you can deploy your application so anyone can visit your application.

Deploy with Heroku

What is deploying?

When you're finished building your application you might want to have the ability for others to see your application. That's possible by deploying your application to a cloud application platform also known as Platform as a Service, meaning it is an environment build for developing, running and managing user made applications without the complexity of building and maintaining the technology behind developing and deploying an application. By deploying an app you essentially are placing your app on a live server that corresponse with a website people can go to to see your application.

Deploying an app on Heroku

Heroku is one of the cloud platforms available to deploy your application. To deploy with Heroku you first have to create an Heroku account. After you created an account you can create a new app by pressing the new button and selecting create new app. Then you see an option to name the application and set the region your application will be running. After you entered the name and region you press the create app button to continue. Then you see the Deploy section allowing you different ways of deploying you application. You can use the Heroku CLI to deploy your app, but you can also connect to GitHub and link your application with Heroku. When you've linked to your repository where you saved your code you might have to take several steps to have Heroku understand what application is running.

First you have to declare the app dependencies. Without declaring the dependencies inside package.json Heroku will not install the dependencies your application is build on and fails to deploy. You also have to state in your package.json which version of node the application is using. By doing this Heroku can use the same version the app is running, instead of guessing which version you're using and maybe failing to run the application.

Then you have two ways of specifying on how the application can start running. The first option is to create a Procfile. In this file you write web: with node as the framework and server.js as your file the application code is in. The second option is to create an npm script to start your application. The default start script to run your application looks as follows:

{
"scripts": {
    "start": "node server.js"
  },
}

Here the node server is running on the server.js file where all the code is located to run the application.

To be sure unnecessary code isn't inside your repository and read by Heroku, you can create a .gitignore file. Inside this file you place the folder or filename that you don't to be saved in your repository.

The last step to deploy the application is to use the command or if you want to connect to GitHub by linking Heroku to Github. I'll only be writing the steps to deploy the application using the command line interface, because the command line is more technical to understand.

To deploy the application using the command line you first have to install the Heroku command line interface (CLI). Heroku as an article named The Heroku CLI which describes how to install the Heroku CLI for your operating system. When installed you start by logging in to your Heroku account. Type heroku login to connect to your account.

heroku login

After you've typed heroku login it will send you to the browser asking you to login. If you're done logging in you will see in the command line that logging in is done and that you've successfully logged in to your account.

To use an existing repository you type heroku git:remote -a <your-repository-name>. This will add the repository to Heroku.

heroku git:remote -a my-repository

The last command you have to do for Heroku to build your live application is to push the code to Heroku by typing git push heroku master.

git push heroku master

After you pushed to master it will display a link of the website your application is running.

Now you have your application be available for anyone to visit. If you have used confidential information in your code it is recommended to use configuration variables.

Configuration Variables

When creating an app that uses secrets in your code to for example connect to a online database, you don't want to share inside your code. The solution is to use configuration variables. These are variables you declare in a seperate file that you don't share in your repository but are used to configure the information needed for connecting to a database or an API. In Node.js there is a module called dotenv you can use to save your secret information. To install dotenv simply type the follwing in the command line:

npm install dotenv

When dotenv is installed you can create an .env file. Inside the file you state the variable, commonly written in capital letters, with an equal sign and your information in plain text.

# example
SECRET=98NCp8w*bD%V8j

Inside your application file you have to import dotenv and use the config function to be able to use the information stored in the .env file.

const express = require("express")
const dotenv = require("dotenv") // import dotenv dependency

dotenv.config() // configure dotenv to use information saved in .env

const app = express()
const port = 8000

app.get('/', function (req, res) {
   res.send(process.env.SECRET) // convert information from .env variable
})

app.listen(port, function () {
        console.log(`Listening on port ${port}`);
    });

In this example the secret will be visible when you go to the index route of the application.

Now the secret variables are declared and proccessed. When you deploy your application with secret variables, you have to state these variables, inside your .env file, on Heroku in order to have the application be able to work. To do that you have two options. You can either use the command line with the heroku config command to view, set or remove configuration variables or you can use the Heroku Dashboard to type the variables individually by going into the Settings tab. I will describe how to use the command line to set secret variables.

To use configuration variables you can use four commands to:

  1. View all configuration variables
  2. View a single secret variable
  3. Add a new secret variable
  4. Delete a secret variable

To see how to set a new variable to Heroku go to Add a new secret variable.

View all configuration variables

The first command shows how to view all variables added to the application on Heroku. To view all variables type heroku config in the command line.

$ heroku config
NAME=JohnDoe
SECRET=98NCp8w*bD%V8j
View a single secret variable

To view one secret variable type heroku config:get <variable-name> in the command line.

$ heroku config:get NAME
JohnDoe
Add a new secret variable

To add a variable to Heroku type heroku condig:set <variable> in the command line.

$ heroku config:set API_KEY=3vBa$=6Vh0dJ
Adding config vars and restarting myapp... done, v12
API_KEY: 3vBa$=6Vh0dJ
Delete a secret variable

To delete a variable from Heroku type heroku config:unset <variable-name> in the command line.

$ heroku config:unset SECRET
Unsetting SECRET and restarting myapp... done, v13

Resources

The resources used for the topics are listed here.

Backend structure

Resources used in topics about the backend structure.

Route modules

Middleware

Error handling

MVC model

Security

Resources used in topics about security.

HTTP Header

Ratelimiter

Hashing

Authentication / Authorization

Deployment

Resources used in topics about deployment.

Deploy with Heroku