Node.js is a popular runtime to write apps for. These apps are often production quality apps that are used by many people. To make maintaining them easier, we’ve to set some guidelines for people to follow.
In this article, we’ll look at some basic security practices to be aware of writing Node apps.
Prevent Query Injection Vulnerabilities with ORM/ODM Libraries
We should never pass in user-inputted strings straight into our app to prevent SQL or NoSQL injection attacks. Inputs should be validated and sanitized before being passed into database queries.
All reputable data access libraries like Sequelize, Knex, and Mongoose have built-in protection against script injection attacks.
Unsanitized strings can easily destroy data and expose them to unauthorized parties if they’re left unsanitized.
Collection of Generic Security Best Practices
We should keep up-to-date with general security best practices so we can implement them when we’re developing and running apps.
Adjust the HTTP Response Headers for Enhanced Security
We can use modules like helmet
to secure headers to prevent attacks from using common attacks like cross-site scripting with our apps.
To add helmet
and use it, we run:
npm i helmet
and then use it as follows:
const express = require('express');
const bodyParser = require('body-parser');
const helmet = require('helmet');
const app = express();
app.use(helmet());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {
res.send('hello');
});
app.listen(3000, () => console.log('server started'));
Helmet automatically protects us from cross-site scripting, enables strict transport security, and keep clients from sniffing the MIME types from responses.
The X-Powered-By
header is also removed from the response so that attackers won’t know that our app is an Express app.
Constantly and Automatically Inspect for Vulnerable Dependencies
We can use npm audit
or snyk to check for packages with vulnerable dependencies before going to production. Otherwise, attacks may take advantage of the vulnerabilities to commit attacks.
Avoid Using the Node.js crypto Library for Handling Passwords, use Bcrypt
bcrypt
provides hash and salt functionality. Therefore it’s better for handling secrets than the built-in crypto library. It’s also faster.
We don’t want attackers to be able to brute-force passwords and tokens with dictionary attacks.
Escape HTML, JS and CSS Output
We should escape these kinds of code so that attacks can’t run malicious client-side code with our app. Dedicated libraries can explicitly mark the data as pure content and should never be executed.
Validate Incoming JSON Schemas
JSON schemas should be validated to make sure that the income request payload has valid data. For instance, we can use the jsonschema
library to validate the structure and values of the JSON that’s sent.
We can use the jsonschema
library as follows with an Express app:
const express = require('express');
const bodyParser = require('body-parser');
const Validator = require('jsonschema').Validator;
const v = new Validator();
const app = express();
const addressSchema = {
"id": "/SimpleAddress",
"type": "object",
"properties": {
"address": { "type": "string" },
},
"required": ["address"]
};
const schema = {
"id": "/SimplePerson",
"type": "object",
"properties": {
"name": { "type": "string" },
"address": { "$ref": "/SimpleAddress" },
},
"required": ["name", "address"]
};
v.addSchema(addressSchema, '/SimpleAddress');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/person', (req, res) => {
if (v.validate(req.body, schema).errors.length) {
return res.send(400)
}
res.send('success');
});
app.listen(3000, () => console.log('server started'));
In the code above, we required the jsonschema
library and use its validator. Then we defined the /SimpleAddress
schema, which is referenced by the /SimplePerson
schema.
We add the /SimpleAddress
schema with:
v.addSchema(addressSchema, '/SimpleAddress');
to reference it in /SimplePerson
.
Then we can check our request body against our schema with:
v.validate(req.body, schema).errors.length
Then we stop the request from proceeding if the request body fails validation.
Support blacklisting JWTs
JSON Web Tokens (JWTs) that were used for malicious user activity should be revoked. Therefore, our app needs a way to revoke these tokens.
Conclusion
We should secure our app by checking for vulnerabilities and revoking tokens that were used for malicious purposes. Also, we need to take steps to prevent malicious from running on client and server-side by sanitizing data everywhere.
Finally, we should validate request bodies to make sure that valid data is submitted to our app.