-
Notifications
You must be signed in to change notification settings - Fork 1
Create a secure & structered web application
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.
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.
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.
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.
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
.
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.
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 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.
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 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.
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 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.
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.
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.
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.
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.
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.
Hashing is a method where a computer converts and is mixing an input to a string with letters and numbers with a specific length.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
- View all configuration variables
- View a single secret variable
- Add a new secret variable
- Delete a secret variable
To see how to set a new variable to Heroku go to Add a new secret variable.
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
To view one secret variable type heroku config:get <variable-name>
in the command line.
$ heroku config:get NAME
JohnDoe
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
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
The resources used for the topics are listed here.
Resources used in topics about the backend structure.
- Javascript: Import & Export
- ES6 Modules and How to Use Import and Export in JavaScript
- Route Handling - Express
- Importing and creating modules - MDN
- Creating route handlers
- What is Middleware? A simple explanation.
- Using Middleware - Express
- What does middleware and app.use actually mean in Expressjs? - Stackoverflow
- Error handling - Express Guide
- Error Handling in Node.js
- Error handling patterns in Express - Github Gist
- HTTP response status codes - MDN
Resources used in topics about security.
- Content Security Policy - Stackoverflow
- Content Security Policy - MDN
- CSP: A successful mess between hardening and mitigation - Speakerdeck
- Content Security Policy Cheat Sheet
- Modern Token Authentication (JWT) in Node with Express
- How to Manage Session using Node.js and Express
- Securing your API’s using Node.je, Mongodb and JWT - Medium
- Session vs Token Based Authentication - Medium
- Authentication on the Web (Sessions, Cookies, JWT, localStorage, and more) - YouTube
Resources used in topics about deployment.