Table of contents
Web security is critical for any web application and encompasses a wide range of issues. This article will discuss three important aspects of web security: logging, rate-limiting, and hashing. We'll look at how to put these features into an Express.js web app.
Logging
The process of recording events and activities that occur within an application is known as logging. It is an essential component of any application because it assists developers in tracking down bugs, diagnosing performance issues, and detecting security breaches. We will use two logging mechanisms in our Express.js application: the console logger and the Winston logger.
function httpconsoleMW(req, res, next) {
console.info(`${req.method} ${req.originalUrl}`)
console.info(`IP Address: ${req.ip}`)
console.info(`Headers: ${JSON.stringify(req.headers)}`)
const start = Date.now();
res.on("finish", () => {
console.info(`status: ${res.statusCode}`)
const elapsed = Date.now() - start;
console.info(`Elapsed time: ${elapsed} ms`)
})
next()
}
The above code is a middleware function that logs incoming requests and outgoing responses. It makes use of the built-in logger in Node.js that allows us to log messages to the console. It logs the HTTP method
, URL
, IP address
, request headers
, status code
, and elapsed time
for each request. This information can be used to monitor the performance of the application and to debug issues.
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
),
transports: [
new winston.transports.Console({
level: 'info',
maxsize: 5242880,
maxFiles: 5,
tailable: true,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.splat(),
winston.format.json()
)
}),
new winston.transports.File({
filename: './logs/error.log',
level: 'error',
maxsize: 1000000,
maxFiles: 5,
tailable: true,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
)
}),
new winston.transports.File({
filename: './logs/warn.log',
level: 'warn',
maxsize: 1000000,
maxFiles: 10,
tailable: true,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
)
})
],
exitOnError: false
});
In the above code, Winston is used to creating a logger object that logs information to the console as well as two separate log files (error.log and warn.log). We set the log level to 'info,' which means that all events with a severity level of 'info' or higher will be logged. We also specify the log format, as well as the maximum file size and the number of log files to retain.
To use the logger object in our application, we must import it and then call the appropriate logging method, such as logger.info()
, logger.error()
, or logger. warn()
.
Rate Limiting
Rate limiting is a technique used to prevent a specific IP address from making an excessive number of requests to a web application. This technique aids in the protection of the web application from overloading and potential Denial of Service (DoS) attacks.
const rateLimit = require('express-rate-limit')
const limiter = rateLimit({
windowMs: 15 * 60 * 100, // 15 minutes
max: 10, //Limit each IP to 20 request per window (per 20 minutes)
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
message: "Too many request, please try again later"
})
To implement rate limiting in a web application, we use the "express-rate-limit" library. The library allows us to specify the maximum number of requests per period for a specific IP address. In the above code, we set the maximum number of requests to 10 per 15 minutes. If the number of requests exceeds this limit, the message "Too many requests, please try again later" will be returned.
Hashing
Hashing is a technique used to protect user passwords in a web application. Hashing converts the plain-text password into an encrypted form, which is difficult to reverse engineer. By hashing passwords, we can ensure that even if a database is compromised, the passwords cannot be easily decrypted.
const mongoose = require('mongoose')
const bcrypt = require('bcrypt')
const Schema = mongoose.Schema
// Define Uuser schema
const userSchema = new Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
}
})
userSchema.pre('save', async function hashedPassword (next) {
// Only run this function if password was actually modified
if (!this.isModified('password')) return next()
// passport hashing
const salt = await bcrypt.genSalt(10)
this.password = await bcrypt.hash(this.password, salt)
next()
})
// method for comparing a user's password with the inputted password.
userSchema.methods.comparePassword = async function(inputPassword) {
return await bcrypt.compare(inputPassword, this.password);
}
The provided code example uses the bcrypt
library to hash user passwords. The library uses the bcrypt algorithm, which is a widely used and highly secure password hashing function. We implement the pre-save hook in your user schema to hash the user password before saving it to the database. Also, we have a comparePassword
method that takes in a plain text password and compares it to the hashed password in the database.
Overall, using hashing to secure sensitive information is an essential aspect of web security. It's crucial to implement strong hashing algorithms to ensure that user information is secure.
Conclusion
Logging, rate-limiting, hashing, and web security are essential aspects of any web application. By incorporating these features, you can build a more robust and secure application that can withstand malicious attacks.
Overall, these are just a few of the many techniques you can employ to strengthen the security of your web application. You can create a safer and more secure web application by following best practices and implementing robust security features.
##