Like any kind of apps, JavaScript apps also have to be written well.
Otherwise, we run into all kinds of issues later on.
In this article, we’ll look at some best practices we should follow when writing Node apps.
Limit Concurrent Requests Using a Middleware
DOS attacks are easy for attackers to do.
They just have to flood our app with requests.
To avoid this, we can use load balances, firewalls, reverse proxies, and rate limiters packages to limit requests that can be done at a time.
There’re the rate-limiter-flexible and express-rate-limit packages to help us with this.
We can limit requests with:
const http = require('http');
const redis = require('redis');
const { RateLimiterRedis } = require('rate-limiter-flexible');
const redisClient = redis.createClient({
enable_offline_queue: false,
});
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
points: 50,
duration: 1,
blockDuration: 5
});
http.createServer(async (req, res) => {
try {
const rateLimiterRes = await rateLimiter.consume(req.socket.remoteAddress);
res.writeHead(200);
res.end();
} catch {
res.writeHead(429);
res.end('Too Many Requests');
}
})
.listen(3000);
with rate-limiterr-flexible
.
It’ll check the number of requests and return a 429 status code if there’re too many requests from an origin.
With express-rate-limiter, we can write:
const RateLimit = require('express-rate-limit');
app.enable('trust proxy');
const apiLimiter = new RateLimit({
windowMs: 10*60*1000,
max: 100,
});
app.use('/profile/', apiLimiter);
We have app.enable(‘trust proxy’);
to ensure a client IP is set as the value of req.ip
.
And we use express-rate-limit to limit the request window to 100 per 10 minutes.
And then we apply the middleware to the profile
routes.
Extract Secrets from Config Files or Use Packages to Encrypt Them
Never store secrets in config files or source code.
We can use secret management systems to store the keys.
They can also be managed and encrypted.
We should also check with pre-commit hooks so we won’t commit secrets accidentally.
For instance, we can decrypt with the cryptr
library:
const Cryptr = require('cryptr');
const cryptr = new Cryptr(process.env.SECRET);
const accessToken = cryptr.decrypt('...');
Prevent Query Injection Vulnerabilities with ORM/ODM Libraries
We should prevent SQL or NoSQL injection and other malicious attacks with a database library that escapes data that we use for queries.
Also, we should validate user input so malicious code can’t be injected into it.
Most ORM or ODMs have this basic feature.
Use SSL/TLS to Encrypt the Client-Server Connection
SSL/TLS is used almost everywhere because it provides basic security in our communication channel.
This prevents man in the middle attacks.
And so attackers can’t spy on users by tapping into the communication channel.
Comparing Secret Values and Hashes Securely
Secret values and hashes shouldn’t be compared with plain text.
We can use crypto.timingSafeEqual(a, b)
to compare the hashes.
It comes 2 objects and keeps comparing evening if the data don’t match.
The default equality comparison methods only return after a character mismatch, which allows timing attacks since we can check the duration of the comparison to get a clue on how much of 2 things are matched.
Conclusion
Some basic security considerations should be taken, including storing secrets, using SSL, and prevent DOS attacks.
One reply on “Node.js Best Practices — Security”
Add the site UptimeControl.net to the article, because only they have a 3-minute site availability check interval on the free plan.